From 392b103b4dc66998c8237e6df5b39cae44362d68 Mon Sep 17 00:00:00 2001 From: Harry <114489245+HyemIin@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:50:41 +0900 Subject: [PATCH 001/685] Create README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..b900e42e --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +NoWait 대학교 축제 내 주점을 웨이팅할 수 있는 서비스입니다. + +🍺 주점 웨이팅 시스템 기능 명세 축제 기간 주점 운영을 위한 실시간 웨이팅/관리 시스템 기능 목록입니다. 아래는 사용자 및 관리자 기능을 기준으로 정리한 전체 기능 목록입니다. + +👤 사용자 기능 + +주점 목록 조회 웨이팅 인원이 적은 순으로 정렬된 주점 목록을 노출 매장명, 위치, 오픈 여부, 대표 이미지 제공 +주점 검색 키워드로 주점 이름 및 설명에서 검색 검색 결과가 없을 경우 안내 문구 노출 +주점 상세 조회 매장 이미지, 설명, 위치, 영업 상태 확인 영업 중일 경우 웨이팅 신청 버튼 활성화 +접속 대기열 페이지 접속 폭주 시 진입 현재 대기 인원 수 및 나의 대기 번호 확인 가능 +메뉴 조회 주점에 등록된 메뉴 목록 확인 메뉴 이름, 가격, 이미지 제공 품절 메뉴는 뱃지 또는 비활성화 처리 🛠 관리자 기능 +주점 등록 주점명, 썸네일 이미지, 위치, 설명 입력 관리자당 1개 주점만 등록 가능 +주점 수정 주점 이미지, 이름, 위치, 설명 개별 수정 이미지 5개 이하 등록 가능 (파일 제한 있음) +주점 삭제 등록된 주점을 삭제하며 관련 웨이팅도 함께 제거 삭제 전 확인 팝업 제공 +메뉴 등록 메뉴 이름, 설명, 가격, 이미지(1장), 품절 여부 입력 등록 성공 시 목록에 반영 +메뉴 수정 이름, 설명, 가격, 이미지 각각 수정 가능 +메뉴 삭제 특정 메뉴를 삭제 삭제 확인 팝업 포함 +주문 내역 확인 사용자 주문 목록을 시간순으로 조회 주문 → 조리중 → 완료 상태 전환 가능 수락 시 “입금 확인” 경고 팝업 제공 +웨이팅 목록 확인 해당 주점의 웨이팅 사용자 목록을 무한스크롤 방식으로 조회 호출, 입장, 취소 등 액션 버튼 포함 +웨이팅 호출 호출 시 카카오톡 알림 전송 동일 사용자 중복 호출 방지 +웨이팅 입장 처리 호출된 사용자를 입장 완료 처리 웨이팅 목록에서 제거 후 ‘입장 완료’ 처리 +웨이팅 취소 처리 노쇼 또는 미입장 사용자를 취소 처리 알림톡 전송 및 목록에서 제거 From dcb9f09755aee6ecfc8db20491ef9375e6369ef9 Mon Sep 17 00:00:00 2001 From: Harry <114489245+HyemIin@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:55:25 +0900 Subject: [PATCH 002/685] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1 @@ + From 0a4cd84d9672697bdc7b8bff70bf386317065850 Mon Sep 17 00:00:00 2001 From: Harry <114489245+HyemIin@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:55:44 +0900 Subject: [PATCH 003/685] Add files via upload --- ...13\341\205\265\341\204\211\341\205\262.md" | 14 ++ .github/workflows/dev.yml | 57 +++++++ .github/workflows/pr_notification.yml | 143 ++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 ".github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" create mode 100644 .github/workflows/dev.yml create mode 100644 .github/workflows/pr_notification.yml diff --git "a/.github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" "b/.github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" new file mode 100644 index 00000000..f728915f --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" @@ -0,0 +1,14 @@ +--- +name: 백엔드 이슈 +about: 백엔드와 관련된 이슈 +title: "[백엔드] " +labels: Backend +assignees: '' + +--- + +# 이슈 내용 + + +# 작업 목록 +- [ ] diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 00000000..57f36953 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,57 @@ +name: Dev CICD + +on: + push: + branches: + - develop +env: + PROJECT_NAME: NoWait + AWS_REGION: ap-northeast-2 + AWS_S3_BUCKET: nowait-deploy-github-actions + AWS_CODE_DEPLOY_APPLICATION: nowaiting + AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev + +jobs: + build-with-gradle: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: JDK 17 설치 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + + - name: gradlew에 실행 권한 부여 + run: chmod +x ./gradlew + + - name: Set YML + run: | + mkdir -p src/main/resources + echo "${{ secrets.APPLICATION_DEV_YML }}" | base64 --decode > src/main/resources/application-dev.yml + find src + + - name: 프로젝트 빌드 + run: ./gradlew clean build -x test + + - name: AWS credential 설정 + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} + + - name: S3에 업로드 + run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/$PROJECT_NAME/$GITHUB_SHA.zip --source . + + - name: EC2에 배포 + run: | + aws deploy create-deployment \ + --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ + --s3-location bucket=$AWS_S3_BUCKET,key=$PROJECT_NAME/$GITHUB_SHA.zip,bundleType=zip + + diff --git a/.github/workflows/pr_notification.yml b/.github/workflows/pr_notification.yml new file mode 100644 index 00000000..b8a0e5ef --- /dev/null +++ b/.github/workflows/pr_notification.yml @@ -0,0 +1,143 @@ +name: PR Discord Notification + +on: + pull_request: + types: [opened, closed, reopened] + + schedule: + - cron: '25 11 * * 1-5' # 월~금 오후 8시 25분 (KST = UTC+9) + + workflow_dispatch: + +env: + USERNAME: "돌아왔다 PR 리뷰 시간" + AVATAR_URL: "https://i.ibb.co/GvfVG729/R1280x0-fjpg.jpg" + +jobs: + notify: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install jq + run: sudo apt-get install -y jq + + - name: Assign Random Reviewer + id: assign-reviewer + if: github.event.action == 'opened' || github.event.action == 'reopened' + uses: actions/github-script@v6 + env: + REVIEWER_MAPPING: ${{ secrets.REVIEWER_MAPPING }} + github-token: ${{ secrets.GITHUB_TOKEN }} + with: + script: | + const mappingJson = process.env.REVIEWER_MAPPING; + let githubToDiscordMap; + try { + githubToDiscordMap = JSON.parse(mappingJson); + } catch (error) { + throw new Error('Invalid JSON in REVIEWER_MAPPING secret.'); + } + + const allReviewers = Object.keys(githubToDiscordMap); + const { data: pullRequest } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number + }); + + const prAuthor = pullRequest.user.login; + const reviewers = allReviewers.filter(reviewer => reviewer.toLowerCase() !== prAuthor.toLowerCase()); + + if (reviewers.length === 0) { + throw new Error('No valid reviewers available (all reviewers are PR authors).'); + } + + const randomReviewer = reviewers[Math.floor(Math.random() * reviewers.length)]; + + if (pullRequest.requested_reviewers.length === 0) { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + reviewers: [randomReviewer] + }); + + const discordId = githubToDiscordMap[randomReviewer]; + if (!discordId) { + throw new Error(`Discord ID not found for GitHub user: ${randomReviewer}`); + } + + core.setOutput('reviewer', randomReviewer); + core.setOutput('discord_id', discordId); + } else { + const existingReviewer = pullRequest.requested_reviewers[0].login; + const discordId = githubToDiscordMap[existingReviewer]; + if (!discordId) { + throw new Error(`Discord ID not found for GitHub user: ${existingReviewer}`); + } + + core.setOutput('reviewer', existingReviewer); + core.setOutput('discord_id', discordId); + } + + - name: Send Discord Notification + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + + run: | + prTitle="${{ github.event.pull_request.title }}" + prUser="${{ github.event.pull_request.user.login }}" + prAction="${{ github.event.action }}" + prUrl="${{ github.event.pull_request.html_url }}" + prMerged="${{ github.event.pull_request.merged }}" + prActor="${{ github.actor }}" + prReviewer="${{ steps.assign-reviewer.outputs.reviewer }}" + prReviewerDiscordID="${{ steps.assign-reviewer.outputs.discord_id }}" + + if [ "$prAction" == "opened" ]; then + MESSAGE="**📢 새로운 PR이 도착했습니다!** + - <@${prReviewerDiscordID}> 님 리뷰어로 할당되었습니다!! 🙏 + - PR 제목 : ${prTitle} + - 등록한 사람 : ${prUser} + - 리뷰어 : <@${prReviewerDiscordID}> + - 리뷰하러 가기 >> [Click](${prUrl})" + + elif [ "$prAction" == "closed" ] && [ "$prMerged" == "true" ]; then + MESSAGE="**🎉 PR이 머지되었습니다!** + - PR 제목 : ${prTitle} + - 작업 수행자 : ${prActor} + - PR 확인하기 >> [Click](${prUrl})" + + elif [ "$prAction" == "closed" ]; then + MESSAGE="**🚨 PR이 닫혔습니다!** + - PR 제목 : ${prTitle} + - 작업 수행자 : ${prActor} + - PR 확인하기 >> [Click](${prUrl})" + + elif [ "$prAction" == "reopened" ]; then + MESSAGE="**🔎 PR이 다시 열렸습니다!** + - <@${prReviewerDiscordID}> 님 리뷰어로 할당되었습니다!! 🙏 + - PR 제목 : ${prTitle} + - 작업 수행자 : ${prActor} + - 리뷰어 : <@${prReviewerDiscordID}> + - 리뷰하러 가기 >> [Click](${prUrl})" + + else + MESSAGE="❓ 알 수 없는 PR 이벤트: **${prTitle}** by ${prUser}. **[클릭](${prUrl})**" + fi + + DATA=$(jq -n \ + --arg content "$MESSAGE" \ + --arg username "$USERNAME" \ + --arg avatar_url "$AVATAR_URL" \ + '{content: $content, username: $username, avatar_url: $avatar_url}') + + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X POST -H 'Content-Type: application/json' -d "$DATA" "$DISCORD_WEBHOOK_URL") + + if [ "$RESPONSE" -ne 204 ]; then + echo "Failed to send Discord notification. HTTP Status Code: $RESPONSE" + exit 1 + fi \ No newline at end of file From ed51d00b472b717686498bed91bf60fa325f7cb4 Mon Sep 17 00:00:00 2001 From: Harry <114489245+HyemIin@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:56:24 +0900 Subject: [PATCH 004/685] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8b137891..fb57f1ee 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1 +1,9 @@ +## 작업 요약 +## Issue Link + +## 문제점 및 어려움 + +## 해결 방안 + +## Reference From 44cd187b7a6d7230f710e7e7e49e6efe3169f992 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 27 Jun 2025 17:36:48 +0900 Subject: [PATCH 005/685] =?UTF-8?q?doc:=20READE.md=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index b900e42e..deb463a9 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,81 @@ -NoWait 대학교 축제 내 주점을 웨이팅할 수 있는 서비스입니다. - -🍺 주점 웨이팅 시스템 기능 명세 축제 기간 주점 운영을 위한 실시간 웨이팅/관리 시스템 기능 목록입니다. 아래는 사용자 및 관리자 기능을 기준으로 정리한 전체 기능 목록입니다. - -👤 사용자 기능 - -주점 목록 조회 웨이팅 인원이 적은 순으로 정렬된 주점 목록을 노출 매장명, 위치, 오픈 여부, 대표 이미지 제공 -주점 검색 키워드로 주점 이름 및 설명에서 검색 검색 결과가 없을 경우 안내 문구 노출 -주점 상세 조회 매장 이미지, 설명, 위치, 영업 상태 확인 영업 중일 경우 웨이팅 신청 버튼 활성화 -접속 대기열 페이지 접속 폭주 시 진입 현재 대기 인원 수 및 나의 대기 번호 확인 가능 -메뉴 조회 주점에 등록된 메뉴 목록 확인 메뉴 이름, 가격, 이미지 제공 품절 메뉴는 뱃지 또는 비활성화 처리 🛠 관리자 기능 -주점 등록 주점명, 썸네일 이미지, 위치, 설명 입력 관리자당 1개 주점만 등록 가능 -주점 수정 주점 이미지, 이름, 위치, 설명 개별 수정 이미지 5개 이하 등록 가능 (파일 제한 있음) -주점 삭제 등록된 주점을 삭제하며 관련 웨이팅도 함께 제거 삭제 전 확인 팝업 제공 -메뉴 등록 메뉴 이름, 설명, 가격, 이미지(1장), 품절 여부 입력 등록 성공 시 목록에 반영 -메뉴 수정 이름, 설명, 가격, 이미지 각각 수정 가능 -메뉴 삭제 특정 메뉴를 삭제 삭제 확인 팝업 포함 -주문 내역 확인 사용자 주문 목록을 시간순으로 조회 주문 → 조리중 → 완료 상태 전환 가능 수락 시 “입금 확인” 경고 팝업 제공 -웨이팅 목록 확인 해당 주점의 웨이팅 사용자 목록을 무한스크롤 방식으로 조회 호출, 입장, 취소 등 액션 버튼 포함 -웨이팅 호출 호출 시 카카오톡 알림 전송 동일 사용자 중복 호출 방지 -웨이팅 입장 처리 호출된 사용자를 입장 완료 처리 웨이팅 목록에서 제거 후 ‘입장 완료’ 처리 -웨이팅 취소 처리 노쇼 또는 미입장 사용자를 취소 처리 알림톡 전송 및 목록에서 제거 +# GTable +대학교 축제 내 주점을 웨이팅할 수 있는 서비스입니다. + +# 🍺 주점 웨이팅 시스템 기능 명세 + +축제 기간 주점 운영을 위한 실시간 웨이팅/관리 시스템 기능 목록입니다. +아래는 사용자 및 관리자 기능을 기준으로 정리한 전체 기능 목록입니다. + +--- + +## 👤 사용자 기능 + +### 1. 주점 목록 조회 +- 웨이팅 인원이 적은 순으로 정렬된 주점 목록을 노출 +- 매장명, 위치, 오픈 여부, 대표 이미지 제공 + +### 2. 주점 검색 +- 키워드로 주점 이름 및 설명에서 검색 +- 검색 결과가 없을 경우 안내 문구 노출 + +### 3. 주점 상세 조회 +- 매장 이미지, 설명, 위치, 영업 상태 확인 +- 영업 중일 경우 웨이팅 신청 버튼 활성화 + +### 4. 접속 대기열 페이지 +- 접속 폭주 시 진입 +- 현재 대기 인원 수 및 나의 대기 번호 확인 가능 + +### 5. 메뉴 조회 +- 주점에 등록된 메뉴 목록 확인 +- 메뉴 이름, 가격, 이미지 제공 +- 품절 메뉴는 뱃지 또는 비활성화 처리 + +--- + +## 🛠 관리자 기능 + +### 6. 주점 등록 +- 주점명, 썸네일 이미지, 위치, 설명 입력 +- 관리자당 1개 주점만 등록 가능 + +### 7. 주점 수정 +- 주점 이미지, 이름, 위치, 설명 개별 수정 +- 이미지 5개 이하 등록 가능 (파일 제한 있음) + +### 8. 주점 삭제 +- 등록된 주점을 삭제하며 관련 웨이팅도 함께 제거 +- 삭제 전 확인 팝업 제공 + +### 9. 메뉴 등록 +- 메뉴 이름, 설명, 가격, 이미지(1장), 품절 여부 입력 +- 등록 성공 시 목록에 반영 + +### 10. 메뉴 수정 +- 이름, 설명, 가격, 이미지 각각 수정 가능 + +### 11. 메뉴 삭제 +- 특정 메뉴를 삭제 +- 삭제 확인 팝업 포함 + +### 12. 주문 내역 확인 +- 사용자 주문 목록을 시간순으로 조회 +- 주문 → 조리중 → 완료 상태 전환 가능 +- 수락 시 “입금 확인” 경고 팝업 제공 + +### 13. 웨이팅 목록 확인 +- 해당 주점의 웨이팅 사용자 목록을 무한스크롤 방식으로 조회 +- 호출, 입장, 취소 등 액션 버튼 포함 + +### 14. 웨이팅 호출 +- 호출 시 카카오톡 알림 전송 +- 동일 사용자 중복 호출 방지 + +### 15. 웨이팅 입장 처리 +- 호출된 사용자를 입장 완료 처리 +- 웨이팅 목록에서 제거 후 ‘입장 완료’ 처리 + +### 16. 웨이팅 취소 처리 +- 노쇼 또는 미입장 사용자를 취소 처리 +- 알림톡 전송 및 목록에서 제거 + From baa577fb1407483668d27ee66a767a1db54b3f8f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 27 Jun 2025 17:37:15 +0900 Subject: [PATCH 006/685] =?UTF-8?q?doc:=20README.md=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index deb463a9..d1f28a20 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ 축제 기간 주점 운영을 위한 실시간 웨이팅/관리 시스템 기능 목록입니다. 아래는 사용자 및 관리자 기능을 기준으로 정리한 전체 기능 목록입니다. + --- ## 👤 사용자 기능 From 483b54eb0322e0a297f0f69a4aa4d155bf983d01 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 27 Jun 2025 18:03:58 +0900 Subject: [PATCH 007/685] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationconfig/config/CorsConfig.java | 2 +- .../adminsecurity/auth/config/CorsConfig.java | 28 ------------------- .../com/nowait/auth/config/CorsConfig.java | 28 ------------------- 3 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 security-admin/src/main/java/com/nowait/adminsecurity/auth/config/CorsConfig.java delete mode 100644 security-front/src/main/java/com/nowait/auth/config/CorsConfig.java diff --git a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java b/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java index d8615194..6d0a6770 100644 --- a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java +++ b/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:3000")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/CorsConfig.java b/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/CorsConfig.java deleted file mode 100644 index 0c8ce56d..00000000 --- a/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/CorsConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.nowait.adminsecurity.auth.config; - -import java.util.List; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -@Configuration -public class CorsConfig { - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - - config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:8083")); // 허용할 출처 설정 - config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 - config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 - config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - // config.setAllowCredentials(true); // 쿠키 포함 허용 - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 - return source; - } -} diff --git a/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java b/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java deleted file mode 100644 index 34b293b5..00000000 --- a/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.nowait.auth.config; - -import java.util.List; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -@Configuration -public class CorsConfig { - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - - config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:8083")); // 허용할 출처 설정 - config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 - config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 - config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - // config.setAllowCredentials(true); // 쿠키 포함 허용 - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 - return source; - } -} From fa447af1301b349599d47feb6c8eaf77e7bf9394 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 27 Jun 2025 18:22:57 +0900 Subject: [PATCH 008/685] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/example/nowaiting/NowaitingApplication.java | 13 ------------- src/main/resources/application.properties | 1 - .../nowaiting/NowaitingApplicationTests.java | 13 ------------- 3 files changed, 27 deletions(-) delete mode 100644 src/main/java/org/example/nowaiting/NowaitingApplication.java delete mode 100644 src/main/resources/application.properties delete mode 100644 src/test/java/org/example/nowaiting/NowaitingApplicationTests.java diff --git a/src/main/java/org/example/nowaiting/NowaitingApplication.java b/src/main/java/org/example/nowaiting/NowaitingApplication.java deleted file mode 100644 index 489db5d7..00000000 --- a/src/main/java/org/example/nowaiting/NowaitingApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.example.nowaiting; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class NowaitingApplication { - - public static void main(String[] args) { - SpringApplication.run(NowaitingApplication.class, args); - } - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 6de0033f..00000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=nowaiting diff --git a/src/test/java/org/example/nowaiting/NowaitingApplicationTests.java b/src/test/java/org/example/nowaiting/NowaitingApplicationTests.java deleted file mode 100644 index 2465630a..00000000 --- a/src/test/java/org/example/nowaiting/NowaitingApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.example.nowaiting; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class NowaitingApplicationTests { - - @Test - void contextLoads() { - } - -} From 19746d72095acdc34288dd6dfde42fe35aaa698a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 27 Jun 2025 19:35:21 +0900 Subject: [PATCH 009/685] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_notification.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pr_notification.yml b/.github/workflows/pr_notification.yml index b8a0e5ef..74cd0f47 100644 --- a/.github/workflows/pr_notification.yml +++ b/.github/workflows/pr_notification.yml @@ -3,9 +3,6 @@ name: PR Discord Notification on: pull_request: types: [opened, closed, reopened] - - schedule: - - cron: '25 11 * * 1-5' # 월~금 오후 8시 25분 (KST = UTC+9) workflow_dispatch: @@ -140,4 +137,4 @@ jobs: if [ "$RESPONSE" -ne 204 ]; then echo "Failed to send Discord notification. HTTP Status Code: $RESPONSE" exit 1 - fi \ No newline at end of file + fi From 42ef25b00e52b941f3db33a9702ffd67df243278 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:17:34 +0900 Subject: [PATCH 010/685] =?UTF-8?q?feat(Menu):=20isSoldOut,=20deleted=20?= =?UTF-8?q?=EC=B9=BC=EB=9F=BC=20=EB=B0=8F=20Update,=20Delete=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/menu/entity/Menu.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/domain-menu/src/main/java/com/example/menu/entity/Menu.java b/domain-menu/src/main/java/com/example/menu/entity/Menu.java index 5ac6ceef..711bc779 100644 --- a/domain-menu/src/main/java/com/example/menu/entity/Menu.java +++ b/domain-menu/src/main/java/com/example/menu/entity/Menu.java @@ -29,13 +29,28 @@ public class Menu extends BaseTimeEntity { private String name; private String description; private Integer price; + private Boolean isSoldOut; + private Boolean deleted; - public Menu(LocalDateTime createdAt, Long id, Long storeId, String name, String description, Integer price) { + + public Menu(LocalDateTime createdAt, Long id, Long storeId, String name, String description, Integer price, Boolean isSoldOut, Boolean deleted) { super(createdAt); this.Id = id; this.storeId = storeId; this.name = name; this.description = description; this.price = price; + this.isSoldOut = isSoldOut != null ? isSoldOut : false; + this.deleted = deleted != null ? deleted : false; + } + + public void updateInfo(String name, String description, Integer price) { + if (name != null) this.name = name; + if (description != null) this.description = description; + if (price != null) this.price = price; } + + public void markAsDeleted() { this.deleted = true; } + + public void toggleSoldOut() { this.isSoldOut = !this.isSoldOut; } } From e45d52435aa18f14e6d9d2fc05bd5d13bad10fe3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:18:05 +0900 Subject: [PATCH 011/685] =?UTF-8?q?feat(Menu):=20Update,=20Delete=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java b/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java index eb7523b7..cba03e7a 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java @@ -2,7 +2,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -11,6 +13,7 @@ import com.example.apiadmin.menu.dto.MenuCreateRequest; import com.example.apiadmin.menu.dto.MenuCreateResponse; +import com.example.apiadmin.menu.dto.MenuUpdateRequest; import com.example.apiadmin.menu.service.MenuService; import com.nowaiting.common.api.ApiUtils; @@ -43,7 +46,58 @@ public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.getMenusByStoreId(storeId) + menuService.getAllMenusByStoreId(storeId) + ) + ); + } + + @GetMapping("/{storeId}/{menuId}") + public ResponseEntity getMenuById( + @PathVariable Long storeId, + @PathVariable Long menuId + ) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + menuService.getMenuById(storeId, menuId) + ) + ); + } + + + @PatchMapping("/update/{menuId}") + public ResponseEntity updateMenu( + @PathVariable Long menuId, + @Valid @RequestBody MenuUpdateRequest request + ) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + menuService.updateMenu(menuId, request) + ) + ); + } + + @DeleteMapping("/delete/{menuId}") + public ResponseEntity deleteMenu(@PathVariable Long menuId) { + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .body( + ApiUtils.success( + menuService.deleteMenu(menuId) + ) + ); + } + + @PatchMapping("/toggle-soldout/{menuId}") + public ResponseEntity toggleSoldOut(@PathVariable Long menuId) { + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .body( + ApiUtils.success( + menuService.toggleSoldOut(menuId) ) ); } From 512974f72311417e5329f775ad7b6f573b96cc1a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:18:28 +0900 Subject: [PATCH 012/685] =?UTF-8?q?feat(Menu):=20isSoldOut,=20deleted=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/apiadmin/menu/dto/MenuCreateRequest.java | 4 ++++ .../com/example/apiadmin/menu/dto/MenuCreateResponse.java | 4 ++++ .../main/java/com/example/apiadmin/menu/dto/MenuReadDto.java | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java index 518dd06b..5b0dae97 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java @@ -21,6 +21,8 @@ public class MenuCreateRequest { private String description; @NotNull private Integer price; + private Boolean isSoldOut; + private Boolean deleted; public Menu toEntity() { return Menu.builder() @@ -28,6 +30,8 @@ public Menu toEntity() { .name(name) .description(description) .price(price) + .isSoldOut(false) + .deleted(false) .build(); } } diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java index fccba560..c2d59e7f 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java @@ -17,6 +17,8 @@ public class MenuCreateResponse { private String name; private String description; private Integer price; + private Boolean isSoldOut; + private Boolean deleted; private LocalDateTime createdAt; public static MenuCreateResponse fromEntity(Menu menu) { @@ -27,6 +29,8 @@ public static MenuCreateResponse fromEntity(Menu menu) { .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) + .isSoldOut(menu.getIsSoldOut()) + .deleted(menu.getDeleted()) .build(); } } diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java index 17fd26ee..c369f0fc 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java @@ -17,6 +17,8 @@ public class MenuReadDto { private String name; private String description; private Integer price; + private Boolean isSoldOut; + private Boolean deleted; private List images; public static MenuReadDto fromEntity(Menu menu, List images) { @@ -26,6 +28,8 @@ public static MenuReadDto fromEntity(Menu menu, List im .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) + .isSoldOut(menu.getIsSoldOut()) + .deleted(menu.getDeleted()) .images(images) .build(); } From db2d97975b51fe21f4fc034ee817b6651cf8e4c7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:19:02 +0900 Subject: [PATCH 013/685] =?UTF-8?q?refactor(Menu):=20findAllByStoreId=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20findAllByStoreIdAndDeletedFalse?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/menu/repository/MenuRepository.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java b/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java index 4bdde11d..dc1a485c 100644 --- a/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java +++ b/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java @@ -1,6 +1,7 @@ package com.example.menu.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -9,5 +10,6 @@ @Repository public interface MenuRepository extends JpaRepository { - List findAllByStoreId(Long storeId); + List findAllByStoreIdAndDeletedFalse(Long storeId); + Optional findByStoreIdAndIdAndDeletedFalse(Long storeId, Long menuId); } From bd87e5f728e8a0bc9f2523bfd9a212e0fccbbdf6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:19:18 +0900 Subject: [PATCH 014/685] =?UTF-8?q?feat(Menu):=20Update,=20Delete=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiadmin/menu/service/MenuService.java | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java index eedb55a8..2f3c7375 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java @@ -10,6 +10,7 @@ import com.example.apiadmin.menu.dto.MenuImageUploadResponse; import com.example.apiadmin.menu.dto.MenuReadDto; import com.example.apiadmin.menu.dto.MenuReadResponse; +import com.example.apiadmin.menu.dto.MenuUpdateRequest; import com.example.menu.entity.Menu; import com.example.menu.entity.MenuImage; import com.example.menu.repository.MenuImageRepository; @@ -34,10 +35,10 @@ public MenuCreateResponse createMenu(MenuCreateRequest request) { } @Transactional(readOnly = true) - public MenuReadResponse getMenusByStoreId(Long storeId) { - List menus = menuRepository.findAllByStoreId(storeId); + public MenuReadResponse getAllMenusByStoreId(Long storeId) { + List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); - List menuReadRespons = menus.stream() + List menuReadResponse = menus.stream() .map(menu -> { List images = menuImageRepository.findByMenu(menu); List imageDto = images.stream() @@ -47,6 +48,62 @@ public MenuReadResponse getMenusByStoreId(Long storeId) { }) .toList(); - return MenuReadResponse.of(menuReadRespons); + return MenuReadResponse.of(menuReadResponse); } + + @Transactional(readOnly = true) + public MenuReadDto getMenuById(Long storeId, Long menuId) { + Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) + .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + + List images = menuImageRepository.findByMenu(menu); + List imageDto = images.stream() + .map(MenuImageUploadResponse::fromEntity) + .toList(); + + return MenuReadDto.fromEntity(menu, imageDto); + } + + + @Transactional + public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + + menu.updateInfo( + request.getName(), + request.getDescription(), + request.getPrice() + ); + + Menu saved = menuRepository.save(menu); + + List images = menuImageRepository.findByMenu(saved); + List imageDto = images.stream() + .map(MenuImageUploadResponse::fromEntity) + .toList(); + + return MenuReadDto.fromEntity(saved, imageDto); + } + + @Transactional + public String deleteMenu(Long menuId) { + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + + menu.markAsDeleted(); + menuRepository.save(menu); + + return "Menu ID " + menuId + " 삭제되었습니다."; + } + + @Transactional + public Boolean toggleSoldOut(Long menuId) { + Menu menu = menuRepository.findById(menuId) + .orElseThrow(() -> new IllegalArgumentException("해당 메뉴가 존재하지 않습니다.")); + + menu.toggleSoldOut(); + return menu.getIsSoldOut(); + } + } From 3629694f8c06cd8a976f6bf0fbda1aa377a9dbd5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:19:59 +0900 Subject: [PATCH 015/685] =?UTF-8?q?refactor(Menu):=20findAllByStoreIdAndDe?= =?UTF-8?q?letedFalse=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/apiuser/menu/service/MenuService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java index 64b9e0b9..d1dcec11 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java +++ b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java @@ -25,7 +25,7 @@ public class MenuService { @Transactional(readOnly = true) public MenuReadResponse getMenusByStoreId(Long storeId) { - List menus = menuRepository.findAllByStoreId(storeId); + List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); List menuReadResponse = menus.stream() .map(menu -> { From d1042f3159627371957d057bb0248c7cb1b390f7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:20:23 +0900 Subject: [PATCH 016/685] =?UTF-8?q?feat(Menu):=20Update=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20=EC=9C=84=ED=95=9C=20MenuUpdateRequest=20D?= =?UTF-8?q?to=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiadmin/menu/dto/MenuUpdateRequest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java new file mode 100644 index 00000000..bdee0e15 --- /dev/null +++ b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java @@ -0,0 +1,16 @@ +package com.example.apiadmin.menu.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MenuUpdateRequest { + private String name; + private String description; + private Integer price; +} From d644873a1de4e8780ad950b07229119b80eb475f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:21:58 +0900 Subject: [PATCH 017/685] =?UTF-8?q?refactor(Store):=20updateInfo=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?toggleActive=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/domainstore/entity/Store.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/domain-store/src/main/java/com/example/domainstore/entity/Store.java b/domain-store/src/main/java/com/example/domainstore/entity/Store.java index 47166005..17464734 100644 --- a/domain-store/src/main/java/com/example/domainstore/entity/Store.java +++ b/domain-store/src/main/java/com/example/domainstore/entity/Store.java @@ -58,20 +58,16 @@ public Store(LocalDateTime createdAt, Long storeId, Long departmentId, String na } public void updateInfo(String name, String location, String description) { - this.name = name; - this.location = location; - this.description = description; + if (name != null) this.name = name; + if (location != null) this.location = location; + if (description != null) this.description = description; } public void markAsDeleted() { this.deleted = true; } - public void activate() { - this.isActive = true; - } - - public void deactivate() { - this.isActive = false; + public void toggleActive() { + this.isActive = !this.isActive; } } From 3ad9a764e6ff1aa3d979a34f4a511b3c09ec578c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:22:22 +0900 Subject: [PATCH 018/685] =?UTF-8?q?feat(Store):=20toggleActive=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiadmin/store/controller/StoreController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java index 29824348..c4fa5422 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java @@ -86,4 +86,15 @@ public ResponseEntity deleteStore(@PathVariable Long storeId) { ) ); } + + @PatchMapping("/toggle-active/{storeId}") + public ResponseEntity toggleActive(@PathVariable Long storeId) { + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .body( + ApiUtils.success( + storeService.toggleActive(storeId) + ) + ); + } } From 9c431a13ea676c089f28934b9f3c2638b5c05599 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:23:35 +0900 Subject: [PATCH 019/685] =?UTF-8?q?refactor(Store):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20type=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiadmin/store/controller/StoreImageController.java | 5 ++--- .../apiadmin/store/dto/StoreImageUploadResponse.java | 2 -- .../example/apiadmin/store/service/StoreImageService.java | 6 +----- .../java/com/example/domainstore/entity/StoreImage.java | 3 --- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java index 531a1bea..61bfbb34 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java @@ -28,8 +28,7 @@ public class StoreImageController { @PostMapping("/store-images/{storeId}") public ResponseEntity uploadStoreImage( @PathVariable Long storeId, - @RequestParam("files") List files, - @RequestParam(value = "types") List types + @RequestParam("files") List files ) { // TODO 관련 정책 확정되면 메서드로 분리 예정 // 파일 개수 제한 검증 @@ -46,7 +45,7 @@ public ResponseEntity uploadStoreImage( } } - List response = storeImageService.saveAll(storeId, files, types); + List response = storeImageService.saveAll(storeId, files); return ResponseEntity .status(HttpStatus.CREATED) .body( diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java index 998dd0b5..bbc1d9da 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java @@ -10,13 +10,11 @@ public class StoreImageUploadResponse { private final Long id; private final String imageUrl; - private final String type; public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { return StoreImageUploadResponse.builder() .id(storeImage.getId()) .imageUrl(storeImage.getImageUrl()) - .type(storeImage.getType()) .build(); } } diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java index 330f0f9c..1c7bafb4 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java @@ -28,10 +28,7 @@ public class StoreImageService { private final S3Service s3Service; @Transactional - public List saveAll(Long storeId, List files, List types) { - if (files.size() != types.size()) { - throw new IllegalArgumentException("파일과 타입의 개수가 일치해야 합니다."); - } + public List saveAll(Long storeId, List files) { String type = "store"; Store store = storeRepository.findById(storeId) @@ -61,7 +58,6 @@ public List saveAll(Long storeId, List .store(store) .imageUrl(uploadResult.url()) .fileKey(uploadResult.key()) - .type(types.get(i)) .build(); storeImageRepository.save(storeImage); diff --git a/domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java b/domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java index 0ced904e..41f0896f 100644 --- a/domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java +++ b/domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java @@ -36,7 +36,4 @@ public class StoreImage extends BaseTimeEntity { @Column(nullable = false, length = 500) private String fileKey; - - @Column(length = 20) - private String type; } From 6105e099351ae62c2bf26db5cf3f66a45016fd0e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:24:32 +0900 Subject: [PATCH 020/685] =?UTF-8?q?fix(Store):=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiadmin/store/dto/StoreReadResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java index 5e5d0ebb..ac58fae7 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java @@ -11,12 +11,12 @@ @Builder public class StoreReadResponse { - private List storeReadDtos; + private List storeReadDto; private boolean hasNext; - public static StoreReadResponse of(List storeReadDtos, boolean hasNext) { + public static StoreReadResponse of(List storeReadDto, boolean hasNext) { return StoreReadResponse.builder() - .storeReadDtos(storeReadDtos) + .storeReadDto(storeReadDto) .hasNext(hasNext) .build(); } From 8cf238b6d3f58fac8e53641dcc21e25f444c0a65 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:24:56 +0900 Subject: [PATCH 021/685] =?UTF-8?q?feat(Store):=20toggleActive=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiadmin/store/service/StoreService.java | 1 + .../example/apiadmin/store/service/StoreServiceImpl.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java index 00ee1e4a..75fe8b09 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java @@ -18,4 +18,5 @@ public interface StoreService { String deleteStore(Long storeId); + Boolean toggleActive(Long storeId); } diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java index fa04162e..2c04e6e0 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java @@ -103,4 +103,13 @@ public String deleteStore(Long storeId) { return "Store ID " + storeId + " 삭제되었습니다."; } + + @Transactional + public Boolean toggleActive(Long storeId) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("해당 메뉴가 존재하지 않습니다.")); + + store.toggleActive(); + return store.getIsActive(); + } } From 7f78d6e9581ed3808d7448ff125ad4d08301848b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 29 Jun 2025 23:25:24 +0900 Subject: [PATCH 022/685] =?UTF-8?q?refactor(Store):=20toggleActive=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20isActive=20=ED=95=84=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/apiadmin/store/dto/StoreUpdateRequest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java index cd104115..bbe0dfdc 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java @@ -13,5 +13,4 @@ public class StoreUpdateRequest { private String name; private String location; private String description; - private Boolean isActive; } From 36eb65a8085fb982cd60503ab9c97d283f67b5ce Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:14:55 +0900 Subject: [PATCH 023/685] =?UTF-8?q?chore(Store):=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20StoreReadResponse=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiadmin/store/dto/StoreReadResponse.java | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java b/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java deleted file mode 100644 index ac58fae7..00000000 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.apiadmin.store.dto; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class StoreReadResponse { - - private List storeReadDto; - private boolean hasNext; - - public static StoreReadResponse of(List storeReadDto, boolean hasNext) { - return StoreReadResponse.builder() - .storeReadDto(storeReadDto) - .hasNext(hasNext) - .build(); - } -} From d0cd91fd373780cf58071a08e4af7454b59fc8c2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:15:09 +0900 Subject: [PATCH 024/685] =?UTF-8?q?fix(Store):=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiuser/store/dto/StoreReadResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java index a580e562..de5eeb41 100644 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java +++ b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java @@ -11,12 +11,12 @@ @Builder public class StoreReadResponse { - private List storeReadDtos; + private List storeReadDto; private boolean hasNext; - public static StoreReadResponse of(List storeReadDtos, boolean hasNext) { + public static StoreReadResponse of(List storeReadDto, boolean hasNext) { return StoreReadResponse.builder() - .storeReadDtos(storeReadDtos) + .storeReadDto(storeReadDto) .hasNext(hasNext) .build(); } From 0c020bcef5cad2a99bb81da177a4c9696b9ef191 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:15:59 +0900 Subject: [PATCH 025/685] =?UTF-8?q?refactor(Store):=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20getAllStores=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 11 ---------- .../apiadmin/store/service/StoreService.java | 3 --- .../store/service/StoreServiceImpl.java | 21 ------------------- .../apiuser/store/service/StoreService.java | 4 ++++ 4 files changed, 4 insertions(+), 35 deletions(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java index c4fa5422..0189c9a8 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java @@ -40,17 +40,6 @@ public ResponseEntity createStore(@Valid @RequestBody StoreCreateRequest requ ); } - @GetMapping("/all-stores") - public ResponseEntity getAllStores() { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - storeService.getAllStores() - ) - ); - } - @GetMapping("/{storeId}") public ResponseEntity getStoreById(@PathVariable Long storeId) { return ResponseEntity diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java index 75fe8b09..6b230d4b 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java @@ -3,15 +3,12 @@ import com.example.apiadmin.store.dto.StoreCreateRequest; import com.example.apiadmin.store.dto.StoreCreateResponse; import com.example.apiadmin.store.dto.StoreReadDto; -import com.example.apiadmin.store.dto.StoreReadResponse; import com.example.apiadmin.store.dto.StoreUpdateRequest; public interface StoreService { StoreCreateResponse createStore(StoreCreateRequest request); - StoreReadResponse getAllStores(); - StoreReadDto getStoreByStoreId(Long storeId); StoreReadDto updateStore(Long storeId, StoreUpdateRequest request); diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java index 2c04e6e0..4c99105a 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java @@ -9,7 +9,6 @@ import com.example.apiadmin.store.dto.StoreCreateResponse; import com.example.apiadmin.store.dto.StoreImageUploadResponse; import com.example.apiadmin.store.dto.StoreReadDto; -import com.example.apiadmin.store.dto.StoreReadResponse; import com.example.apiadmin.store.dto.StoreUpdateRequest; import com.example.domainstore.entity.Store; import com.example.domainstore.entity.StoreImage; @@ -36,26 +35,6 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { return StoreCreateResponse.fromEntity(saved); } - @Override - @Transactional(readOnly = true) - public StoreReadResponse getAllStores() { - List stores = storeRepository.findAllByDeletedFalse(); - - List storeRead = stores.stream() - .map(store -> { - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - return StoreReadDto.fromEntity(store, imageDto); - }) - .toList(); - - boolean hasNext = false; - - return StoreReadResponse.of(storeRead, hasNext); - } - @Override @Transactional(readOnly = true) public StoreReadDto getStoreByStoreId(Long storeId) { diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java b/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java index fd90de2a..83ea3467 100644 --- a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java +++ b/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java @@ -2,6 +2,8 @@ import java.util.List; +import org.springframework.data.domain.Pageable; + import com.example.apiuser.store.dto.StoreReadDto; import com.example.apiuser.store.dto.StoreReadResponse; @@ -9,6 +11,8 @@ public interface StoreService { StoreReadResponse getAllStores(); + public StoreReadResponse getAllStoresByPage(Pageable pageable); + StoreReadDto getStoreByStoreId(Long storeId); List searchStoresByName(String name); From 2f1ea46d1da23bdc0aea0c37d857c3e439e1bac4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:16:45 +0900 Subject: [PATCH 026/685] =?UTF-8?q?feat(Store):=20getAllStoresByPage=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 16 +++++++++++++- .../apiuser/menu/service/MenuService.java | 15 ++++++++++++- .../store/controller/StoreController.java | 8 +++++++ .../store/service/StoreServiceImpl.java | 21 +++++++++++++++++++ .../repository/StoreRepository.java | 4 ++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java b/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java index d3fb6d02..d1270ccb 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java +++ b/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java @@ -25,7 +25,21 @@ public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.getMenusByStoreId(storeId) + menuService.getAllMenusByStoreId(storeId) + ) + ); + } + + @GetMapping("/{storeId}/{menuId}") + public ResponseEntity getMenuById( + @PathVariable Long storeId, + @PathVariable Long menuId + ) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + menuService.getMenuById(storeId, menuId) ) ); } diff --git a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java index d1dcec11..8763f270 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java +++ b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java @@ -24,7 +24,7 @@ public class MenuService { @Transactional(readOnly = true) - public MenuReadResponse getMenusByStoreId(Long storeId) { + public MenuReadResponse getAllMenusByStoreId(Long storeId) { List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); List menuReadResponse = menus.stream() @@ -39,4 +39,17 @@ public MenuReadResponse getMenusByStoreId(Long storeId) { return MenuReadResponse.of(menuReadResponse); } + + @Transactional(readOnly = true) + public MenuReadDto getMenuById(Long storeId, Long menuId) { + Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) + .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + + List images = menuImageRepository.findByMenu(menu); + List imageDto = images.stream() + .map(MenuImageUploadResponse::fromEntity) + .toList(); + + return MenuReadDto.fromEntity(menu, imageDto); + } } diff --git a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java b/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java index 510b19d7..6a3cba2e 100644 --- a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java +++ b/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java @@ -1,5 +1,6 @@ package com.example.apiuser.store.controller; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -32,6 +33,13 @@ public ResponseEntity getAllStores() { ); } + @GetMapping("/all-stores/infinite-scroll") + public ResponseEntity getAllStores(Pageable pageable) { + return ResponseEntity + .ok() + .body(ApiUtils.success(storeService.getAllStoresByPage(pageable))); + } + @GetMapping("/{storeId}") public ResponseEntity getStoreById(@PathVariable Long storeId) { return ResponseEntity diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java b/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java index d641f03e..c68657f9 100644 --- a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java +++ b/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java @@ -2,6 +2,8 @@ import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.apiuser.store.dto.StoreImageUploadResponse; @@ -43,6 +45,25 @@ public StoreReadResponse getAllStores() { return StoreReadResponse.of(storeRead, hasNext); } + @Transactional(readOnly = true) + public StoreReadResponse getAllStoresByPage(Pageable pageable) { + Slice stores = storeRepository.findAllByDeletedFalseOrderByStoreIdDesc(pageable); + + List storeRead = stores.getContent().stream() + .map(store -> { + List images = storeImageRepository.findByStore(store); + List imageDto = images.stream() + .map(StoreImageUploadResponse::fromEntity) + .toList(); + return StoreReadDto.fromEntity(store, imageDto); + }) + .toList(); + + boolean hasNext = stores.hasNext(); + + return StoreReadResponse.of(storeRead, hasNext); + } + @Override @Transactional(readOnly = true) public StoreReadDto getStoreByStoreId(Long storeId) { diff --git a/domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java b/domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java index 81a08ac2..1d817652 100644 --- a/domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java +++ b/domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -16,4 +18,6 @@ public interface StoreRepository extends JpaRepository { Optional findByStoreIdAndDeletedFalse(Long storeId); List findByNameContainingIgnoreCaseAndDeletedFalse(String name); + + Slice findAllByDeletedFalseOrderByStoreIdDesc(Pageable pageable); } From 3b5a0269e73851cfc334831d8077f9044bc6c426 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:17:05 +0900 Subject: [PATCH 027/685] =?UTF-8?q?feat(Menu):=20isSoldOut,=20deleted=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/apiuser/menu/dto/MenuReadDto.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java index 98dda1f2..1206b870 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java +++ b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java @@ -17,6 +17,8 @@ public class MenuReadDto { private String name; private String description; private Integer price; + private Boolean isSoldOut; + private Boolean deleted; private List images; public static MenuReadDto fromEntity(Menu menu, List images) { @@ -26,6 +28,8 @@ public static MenuReadDto fromEntity(Menu menu, List im .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) + .isSoldOut(menu.getIsSoldOut()) + .deleted(menu.getDeleted()) .images(images) .build(); } From 75b11d8d94dddb590786551011ef8dab454facb2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:17:33 +0900 Subject: [PATCH 028/685] =?UTF-8?q?refactor(Store):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20type=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiuser/store/dto/StoreImageUploadResponse.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java index 40cb984d..23a1b224 100644 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java +++ b/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java @@ -10,13 +10,11 @@ public class StoreImageUploadResponse { private final Long id; private final String imageUrl; - private final String type; public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { return StoreImageUploadResponse.builder() .id(storeImage.getId()) .imageUrl(storeImage.getImageUrl()) - .type(storeImage.getType()) .build(); } } From 2b2a865cc9b6753a38a3a80d91de6038d236adc7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:31:01 +0900 Subject: [PATCH 029/685] =?UTF-8?q?refactor(Store):=20200=20OK=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiadmin/store/controller/StoreController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java index 0189c9a8..fac4a429 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java @@ -79,7 +79,7 @@ public ResponseEntity deleteStore(@PathVariable Long storeId) { @PatchMapping("/toggle-active/{storeId}") public ResponseEntity toggleActive(@PathVariable Long storeId) { return ResponseEntity - .status(HttpStatus.NO_CONTENT) + .status(HttpStatus.OK) .body( ApiUtils.success( storeService.toggleActive(storeId) From 6aa3d1afce21985280478035cb7f26a94ee9f7ec Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:32:55 +0900 Subject: [PATCH 030/685] =?UTF-8?q?fix(Store):=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=B5=EC=88=98=ED=98=95?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiuser/store/dto/StoreReadResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java index de5eeb41..a580e562 100644 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java +++ b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java @@ -11,12 +11,12 @@ @Builder public class StoreReadResponse { - private List storeReadDto; + private List storeReadDtos; private boolean hasNext; - public static StoreReadResponse of(List storeReadDto, boolean hasNext) { + public static StoreReadResponse of(List storeReadDtos, boolean hasNext) { return StoreReadResponse.builder() - .storeReadDto(storeReadDto) + .storeReadDtos(storeReadDtos) .hasNext(hasNext) .build(); } From 0facb5e3e9856be2cc93aa8958bcba1ddc25256a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:38:26 +0900 Subject: [PATCH 031/685] =?UTF-8?q?fix(Store):=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiadmin/store/service/StoreServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java index 4c99105a..ee7be676 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java +++ b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java @@ -86,7 +86,7 @@ public String deleteStore(Long storeId) { @Transactional public Boolean toggleActive(Long storeId) { Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("해당 메뉴가 존재하지 않습니다.")); + .orElseThrow(() -> new IllegalArgumentException("해당 스토어가 존재하지 않습니다.")); store.toggleActive(); return store.getIsActive(); From f2fe0473072aab6206111f52c7a67a1b92593251 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:41:11 +0900 Subject: [PATCH 032/685] =?UTF-8?q?fix(Menu):=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/apiadmin/menu/dto/MenuCreateRequest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java index 5b0dae97..3dc462d2 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java @@ -21,8 +21,6 @@ public class MenuCreateRequest { private String description; @NotNull private Integer price; - private Boolean isSoldOut; - private Boolean deleted; public Menu toEntity() { return Menu.builder() From 29a9c8f4b068f2fc31fb06c2aa04055f2e94c772 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:42:08 +0900 Subject: [PATCH 033/685] =?UTF-8?q?fix(Menu):=20toggleSoldOut=20HttpStatus?= =?UTF-8?q?=20200=20OK=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiadmin/menu/controller/MenuController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java b/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java index cba03e7a..095c2eb4 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java @@ -94,7 +94,7 @@ public ResponseEntity deleteMenu(@PathVariable Long menuId) { @PatchMapping("/toggle-soldout/{menuId}") public ResponseEntity toggleSoldOut(@PathVariable Long menuId) { return ResponseEntity - .status(HttpStatus.NO_CONTENT) + .status(HttpStatus.OK) .body( ApiUtils.success( menuService.toggleSoldOut(menuId) From 5ab51217ce1425873c2f11cda10f99b6e3ff2f33 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:42:52 +0900 Subject: [PATCH 034/685] =?UTF-8?q?fix(Menu):=20deleteMenu=20HttpStatus=20?= =?UTF-8?q?200=20OK=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/apiadmin/menu/controller/MenuController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java b/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java index 095c2eb4..bc709367 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java @@ -83,7 +83,7 @@ public ResponseEntity updateMenu( @DeleteMapping("/delete/{menuId}") public ResponseEntity deleteMenu(@PathVariable Long menuId) { return ResponseEntity - .status(HttpStatus.NO_CONTENT) + .status(HttpStatus.OK) .body( ApiUtils.success( menuService.deleteMenu(menuId) From 3991582abf1db4d755e9461bf5362dca7ee8507c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:45:31 +0900 Subject: [PATCH 035/685] =?UTF-8?q?refactor(Menu):=20update=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20soft=20delete=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/apiadmin/menu/service/MenuService.java | 2 +- .../main/java/com/example/menu/repository/MenuRepository.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java index 2f3c7375..8c0e6c47 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java @@ -67,7 +67,7 @@ public MenuReadDto getMenuById(Long storeId, Long menuId) { @Transactional public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { - Menu menu = menuRepository.findById(menuId) + Menu menu = menuRepository.findByIdAndDeletedFalse(menuId) .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); menu.updateInfo( diff --git a/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java b/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java index dc1a485c..ca15d517 100644 --- a/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java +++ b/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java @@ -12,4 +12,5 @@ public interface MenuRepository extends JpaRepository { List findAllByStoreIdAndDeletedFalse(Long storeId); Optional findByStoreIdAndIdAndDeletedFalse(Long storeId, Long menuId); + Optional findByIdAndDeletedFalse(Long menuId); } From d9f8c83d08b3330002d37d4f6c306759fe416d46 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:46:31 +0900 Subject: [PATCH 036/685] =?UTF-8?q?refactor(Menu):=20deleteMenu=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/apiadmin/menu/service/MenuService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java index 8c0e6c47..73c11fd3 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java @@ -89,12 +89,12 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { @Transactional public String deleteMenu(Long menuId) { Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + .orElseThrow(() -> new IllegalArgumentException("Menu is already deleted with id: " + menuId)); menu.markAsDeleted(); menuRepository.save(menu); - return "Menu ID " + menuId + " 삭제되었습니다."; + return "Menu with ID " + menuId + " 삭제되었습니다."; } @Transactional From de975c135eaa496ae21ed03d21d3126529935354 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 00:47:26 +0900 Subject: [PATCH 037/685] =?UTF-8?q?refactor(Menu):=20toggleSoldOut=20save?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/apiadmin/menu/service/MenuService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java index 73c11fd3..cf8fde2d 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java +++ b/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java @@ -103,6 +103,7 @@ public Boolean toggleSoldOut(Long menuId) { .orElseThrow(() -> new IllegalArgumentException("해당 메뉴가 존재하지 않습니다.")); menu.toggleSoldOut(); + menuRepository.save(menu); return menu.getIsSoldOut(); } From 4fa142123251036dabf1a3b3768c134ada20a528 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 15:38:53 +0900 Subject: [PATCH 038/685] =?UTF-8?q?refactor:=20package=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=9D=BC=EA=B4=80=EB=90=98=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20OAuth=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiadmin/store/service/StoreService.java | 19 --- .../example/apiuser/ApiUserApplication.java | 39 ------- .../.gitattributes | 0 {api-admin => application-admin}/.gitignore | 0 {api-admin => application-admin}/build.gradle | 0 .../ApiAdminApplication.java | 31 +++-- .../exception/GlobalExceptionHandler.java | 20 ++-- .../menu/controller/MenuController.java | 12 +- .../menu/controller/MenuImageController.java | 8 +- .../menu/dto/MenuCreateRequest.java | 4 +- .../menu/dto/MenuCreateResponse.java | 4 +- .../menu/dto/MenuImageUploadResponse.java | 4 +- .../menu/dto/MenuReadDto.java | 4 +- .../menu/dto/MenuReadResponse.java | 2 +- .../menu/dto/MenuUpdateRequest.java | 2 +- .../menu/service/MenuImageService.java | 14 +-- .../menu/service/MenuService.java | 22 ++-- .../order/controller/OrderController.java | 2 +- .../controller/ReservationController.java | 12 +- .../reservation/dto/CallGetResponseDto.java | 4 +- .../dto/ReservationGetResponseDto.java | 4 +- .../dto/ReservationStatusSummaryDto.java | 2 +- .../ReservationStatusUpdateRequestDto.java | 4 +- .../ReservationNotFoundException.java | 4 +- .../service/ReservationService.java | 19 ++- .../store/controller/StoreController.java | 12 +- .../controller/StoreImageController.java | 8 +- .../store/dto/StoreCreateRequest.java | 4 +- .../store/dto/StoreCreateResponse.java | 4 +- .../store/dto/StoreImageUploadResponse.java | 4 +- .../store/dto/StoreReadDto.java | 4 +- .../store/dto/StoreUpdateRequest.java | 2 +- .../store/service/StoreImageService.java | 14 +-- .../store/service/StoreService.java | 19 +++ .../store/service/StoreServiceImpl.java | 20 ++-- .../token/controller/TokenController.java | 8 +- .../token/dto/AuthenticationResponse.java | 2 +- .../token/dto/RefreshTokenRequest.java | 2 +- .../token/service/TokenService.java | 6 +- .../user/controller/UserController.java | 10 +- .../user/dto/ManagerLoginRequestDto.java | 2 +- .../user/dto/ManagerLoginResponseDto.java | 4 +- .../user/dto/ManagerSignupRequestDto.java | 8 +- .../user/dto/ManagerSignupResponseDto.java | 6 +- .../user/dto/UserResponseDto.java | 2 +- .../user/serivce/UserService.java | 16 +-- .../config}/config/AsyncConfig.java | 2 +- .../config}/config/CorsConfig.java | 2 +- {api-user => application-user}/.gitattributes | 0 {api-user => application-user}/.gitignore | 0 {api-user => application-user}/build.gradle | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../applicationuser/ApiUserApplication.java | 40 +++++++ .../controller/BookmarkController.java | 10 +- .../bookmark/dto/BookmarkCreateResponse.java | 4 +- .../bookmark/dto/BookmarkGetResponse.java | 4 +- .../bookmark/service/BookmarkService.java | 20 ++-- .../exception/GlobalExceptionHandler.java | 28 ++--- .../menu/controller/MenuController.java | 6 +- .../menu/dto/MenuImageUploadResponse.java | 4 +- .../menu/dto/MenuReadDto.java | 4 +- .../menu/dto/MenuReadResponse.java | 2 +- .../menu/service/MenuService.java | 16 +-- .../order/controller/OrderController.java | 12 +- .../order/dto/CartItemDto.java | 2 +- .../order/dto/OrderCreateRequestDto.java | 2 +- .../order/dto/OrderCreateResponseDto.java | 4 +- .../dto/OrderItemListGetResponseDto.java | 4 +- .../order/dto/OrderItemResponseDTO.java | 2 +- .../order/service/OrderService.java | 32 +++--- .../controller/ReservationController.java | 12 +- .../dto/ReservationCreateRequestDto.java | 2 +- .../dto/ReservationCreateResponseDto.java | 2 +- .../ReservationNotFoundException.java | 4 +- .../service/ReservationService.java | 24 ++-- .../store/controller/StoreController.java | 6 +- .../store/dto/StoreImageUploadResponse.java | 4 +- .../store/dto/StoreReadDto.java | 4 +- .../store/dto/StoreReadResponse.java | 2 +- .../store/service/StoreService.java | 6 +- .../store/service/StoreServiceImpl.java | 16 +-- .../token/controller/TokenController.java | 10 +- .../token/dto/AuthenticationResponse.java | 2 +- .../token/dto/RefreshTokenRequest.java | 2 +- .../token/service/TokenService.java | 12 +- .../common/api/ApiError.java | 4 +- .../common/api/ApiResult.java | 2 +- .../common/api/ApiUtils.java | 4 +- .../common/enums/HttpStatusCode.java | 2 +- .../common/enums/ReservationStatus.java | 2 +- .../common/enums/Role.java | 2 +- .../common/enums/SocialType.java | 2 +- .../common/exception/ErrorMessage.java | 2 +- .../common/exception/ErrorResponse.java | 2 +- .../entity/Bookmark.java | 6 +- .../BookmarkOwnerMismatchException.java | 4 +- .../exception/DuplicateBookmarkException.java | 4 +- .../repository/BookmarkRepository.java | 8 +- .../{example => nowait}/menu/entity/Menu.java | 2 +- .../menu/entity/MenuImage.java | 2 +- .../menu/repository/MenuImageRepository.java | 6 +- .../menu/repository/MenuRepository.java | 4 +- .../entity/OrderItem.java | 4 +- .../entity/UserOrder.java | 4 +- .../exception/DuplicateOrderException.java | 4 +- .../exception/OrderItemsEmptyException.java | 4 +- .../OrderParameterEmptyException.java | 4 +- .../repository/OrderItemRepository.java | 4 +- .../repository/OrderRepository.java | 4 +- .../entity/Reservation.java | 8 +- .../repository/ReservationRepository.java | 4 +- .../store}/entity/Store.java | 2 +- .../store}/entity/StoreImage.java | 2 +- .../repository/StoreImageRepository.java | 6 +- .../store}/repository/StoreRepository.java | 4 +- .../exception/BusinessException.java | 16 --- .../token}/entity/Token.java | 4 +- .../token}/exception/BusinessException.java | 4 +- .../token}/repository/TokenRepository.java | 4 +- .../token}/DomainTokenApplicationTests.java | 2 +- .../user/entity/MemberDetails.java | 2 +- .../user/entity/User.java | 6 +- .../user/exception/UserNotFoundException.java | 4 +- .../user/repository/UserRepository.java | 4 +- external-oauth/.gitignore | 37 ++++++ external-oauth/build.gradle | 40 +++++++ .../externaloauth}/dto/CustomOAuth2User.java | 4 +- .../externaloauth}/dto/KaKaoResponse.java | 2 +- .../externaloauth}/dto/OAuth2Response.java | 2 +- .../jwt/JwtAuthorizationFilter.java | 10 +- .../nowait/externaloauth}/jwt/JwtUtil.java | 2 +- .../oauth2/CustomOAuth2UserService.java | 16 +-- .../oauth2/OAuth2LoginSuccessHandler.java | 13 ++- .../service/CustomUserDetailService.java | 8 +- .../infraaws}/config/AwsS3Config.java | 2 +- .../infraaws}/s3/S3Service.java | 2 +- .../auth/service/CustomUserDetailService.java | 6 +- .../RefreshTokenNotFoundException.java | 2 +- .../exception/ResourceNotFoundException.java | 2 +- .../exception/TokenBadRequestException.java | 4 +- .../exception/UnauthorizedException.java | 2 +- security-front/build.gradle | 1 + .../com/nowait/exception/ErrorMessage.java | 22 ---- .../com/nowait/exception/ErrorResponse.java | 26 ----- .../exception/GlobalExceptionHandler.java | 108 ------------------ .../RefreshTokenNotFoundException.java | 4 +- .../exception/ResourceNotFoundException.java | 4 +- .../exception/TokenBadRequestException.java | 5 +- .../exception/UnauthorizedException.java | 4 +- settings.gradle | 9 +- 151 files changed, 558 insertions(+), 639 deletions(-) delete mode 100644 api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java delete mode 100644 api-user/src/main/java/com/example/apiuser/ApiUserApplication.java rename {api-admin => application-admin}/.gitattributes (100%) rename {api-admin => application-admin}/.gitignore (100%) rename {api-admin => application-admin}/build.gradle (100%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/ApiAdminApplication.java (51%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/exception/GlobalExceptionHandler.java (89%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/controller/MenuController.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/controller/MenuImageController.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/dto/MenuCreateRequest.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/dto/MenuCreateResponse.java (90%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/dto/MenuImageUploadResponse.java (80%) rename {api-user/src/main/java/com/example/apiuser => application-admin/src/main/java/com/nowait/applicationadmin}/menu/dto/MenuReadDto.java (90%) rename {api-user/src/main/java/com/example/apiuser => application-admin/src/main/java/com/nowait/applicationadmin}/menu/dto/MenuReadResponse.java (88%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/dto/MenuUpdateRequest.java (85%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/service/MenuImageService.java (79%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/menu/service/MenuService.java (82%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/order/controller/OrderController.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/controller/ReservationController.java (83%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/dto/CallGetResponseDto.java (86%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/dto/ReservationGetResponseDto.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/dto/ReservationStatusSummaryDto.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/dto/ReservationStatusUpdateRequestDto.java (59%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/exception/ReservationNotFoundException.java (61%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/reservation/service/ReservationService.java (73%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/controller/StoreController.java (85%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/controller/StoreImageController.java (89%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/dto/StoreCreateRequest.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/dto/StoreCreateResponse.java (90%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/dto/StoreImageUploadResponse.java (80%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/dto/StoreReadDto.java (91%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/dto/StoreUpdateRequest.java (85%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/service/StoreImageService.java (85%) create mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/store/service/StoreServiceImpl.java (81%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/token/controller/TokenController.java (89%) rename {api-user/src/main/java/com/example/apiuser => application-admin/src/main/java/com/nowait/applicationadmin}/token/dto/AuthenticationResponse.java (90%) rename {api-user/src/main/java/com/example/apiuser => application-admin/src/main/java/com/nowait/applicationadmin}/token/dto/RefreshTokenRequest.java (84%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/token/service/TokenService.java (93%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/controller/UserController.java (86%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/dto/ManagerLoginRequestDto.java (94%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/dto/ManagerLoginResponseDto.java (92%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/dto/ManagerSignupRequestDto.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/dto/ManagerSignupResponseDto.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/dto/UserResponseDto.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-admin/src/main/java/com/nowait/applicationadmin}/user/serivce/UserService.java (83%) rename application-config/src/main/java/com/{example/applicationconfig => nowait/config}/config/AsyncConfig.java (92%) rename application-config/src/main/java/com/{example/applicationconfig => nowait/config}/config/CorsConfig.java (96%) rename {api-user => application-user}/.gitattributes (100%) rename {api-user => application-user}/.gitignore (100%) rename {api-user => application-user}/build.gradle (95%) rename {api-user => application-user}/gradle/wrapper/gradle-wrapper.jar (100%) rename {api-user => application-user}/gradle/wrapper/gradle-wrapper.properties (100%) create mode 100644 application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/bookmark/controller/BookmarkController.java (89%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/bookmark/dto/BookmarkCreateResponse.java (83%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/bookmark/dto/BookmarkGetResponse.java (83%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/bookmark/service/BookmarkService.java (82%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/exception/GlobalExceptionHandler.java (88%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/menu/controller/MenuController.java (87%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/menu/dto/MenuImageUploadResponse.java (80%) rename {api-admin/src/main/java/com/example/apiadmin => application-user/src/main/java/com/nowait/applicationuser}/menu/dto/MenuReadDto.java (90%) rename {api-admin/src/main/java/com/example/apiadmin => application-user/src/main/java/com/nowait/applicationuser}/menu/dto/MenuReadResponse.java (89%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/menu/service/MenuService.java (77%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/controller/OrderController.java (86%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/dto/CartItemDto.java (79%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/dto/OrderCreateRequestDto.java (84%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/dto/OrderCreateResponseDto.java (89%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/dto/OrderItemListGetResponseDto.java (87%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/dto/OrderItemResponseDTO.java (88%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/order/service/OrderService.java (82%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/reservation/controller/ReservationController.java (79%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/reservation/dto/ReservationCreateRequestDto.java (72%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/reservation/dto/ReservationCreateResponseDto.java (84%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/reservation/exception/ReservationNotFoundException.java (61%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/reservation/service/ReservationService.java (67%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/store/controller/StoreController.java (90%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/store/dto/StoreImageUploadResponse.java (80%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/store/dto/StoreReadDto.java (91%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/store/dto/StoreReadResponse.java (90%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/store/service/StoreService.java (64%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/store/service/StoreServiceImpl.java (86%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/token/controller/TokenController.java (87%) rename {api-admin/src/main/java/com/example/apiadmin => application-user/src/main/java/com/nowait/applicationuser}/token/dto/AuthenticationResponse.java (90%) rename {api-admin/src/main/java/com/example/apiadmin => application-user/src/main/java/com/nowait/applicationuser}/token/dto/RefreshTokenRequest.java (85%) rename {api-user/src/main/java/com/example/apiuser => application-user/src/main/java/com/nowait/applicationuser}/token/service/TokenService.java (86%) rename common/src/main/java/com/{nowaiting => nowait}/common/api/ApiError.java (89%) rename common/src/main/java/com/{nowaiting => nowait}/common/api/ApiResult.java (92%) rename common/src/main/java/com/{nowaiting => nowait}/common/api/ApiUtils.java (85%) rename common/src/main/java/com/{nowaiting => nowait}/common/enums/HttpStatusCode.java (88%) rename common/src/main/java/com/{nowaiting => nowait}/common/enums/ReservationStatus.java (67%) rename common/src/main/java/com/{nowaiting => nowait}/common/enums/Role.java (94%) rename common/src/main/java/com/{nowaiting => nowait}/common/enums/SocialType.java (62%) rename common/src/main/java/com/{nowaiting => nowait}/common/exception/ErrorMessage.java (96%) rename common/src/main/java/com/{nowaiting => nowait}/common/exception/ErrorResponse.java (94%) rename domain-bookmark/src/main/java/com/nowait/{domainbookmark => bookmark}/entity/Bookmark.java (90%) rename domain-bookmark/src/main/java/com/nowait/{domainbookmark => bookmark}/exception/BookmarkOwnerMismatchException.java (64%) rename domain-bookmark/src/main/java/com/nowait/{domainbookmark => bookmark}/exception/DuplicateBookmarkException.java (63%) rename domain-bookmark/src/main/java/com/nowait/{domainbookmark => bookmark}/repository/BookmarkRepository.java (65%) rename domain-menu/src/main/java/com/{example => nowait}/menu/entity/Menu.java (97%) rename domain-menu/src/main/java/com/{example => nowait}/menu/entity/MenuImage.java (96%) rename domain-menu/src/main/java/com/{example => nowait}/menu/repository/MenuImageRepository.java (70%) rename domain-menu/src/main/java/com/{example => nowait}/menu/repository/MenuRepository.java (85%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/entity/OrderItem.java (92%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/entity/UserOrder.java (93%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/exception/DuplicateOrderException.java (62%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/exception/OrderItemsEmptyException.java (63%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/exception/OrderParameterEmptyException.java (65%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/repository/OrderItemRepository.java (69%) rename domain-order/src/main/java/com/nowait/{domainorder => order}/repository/OrderRepository.java (83%) rename domain-reservation/src/main/java/com/nowait/{domainreservation => reservation}/entity/Reservation.java (88%) rename domain-reservation/src/main/java/com/nowait/{domainreservation => reservation}/repository/ReservationRepository.java (70%) rename domain-store/src/main/java/com/{example/domainstore => nowait/store}/entity/Store.java (97%) rename domain-store/src/main/java/com/{example/domainstore => nowait/store}/entity/StoreImage.java (96%) rename domain-store/src/main/java/com/{example/domainstore => nowait/store}/repository/StoreImageRepository.java (66%) rename domain-store/src/main/java/com/{example/domainstore => nowait/store}/repository/StoreRepository.java (86%) delete mode 100644 domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java rename domain-token/src/main/java/com/{example/domaintoken => nowait/token}/entity/Token.java (94%) rename {security-front/src/main/java/com/nowait => domain-token/src/main/java/com/nowait/token}/exception/BusinessException.java (78%) rename domain-token/src/main/java/com/{example/domaintoken => nowait/token}/repository/TokenRepository.java (70%) rename domain-token/src/test/java/com/{example/domaintoken => nowait/token}/DomainTokenApplicationTests.java (85%) rename domain-user/src/main/java/com/{nowaiting => nowait}/user/entity/MemberDetails.java (97%) rename domain-user/src/main/java/com/{nowaiting => nowait}/user/entity/User.java (94%) rename domain-user/src/main/java/com/{nowaiting => nowait}/user/exception/UserNotFoundException.java (62%) rename domain-user/src/main/java/com/{nowaiting => nowait}/user/repository/UserRepository.java (76%) create mode 100644 external-oauth/.gitignore create mode 100644 external-oauth/build.gradle rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/dto/CustomOAuth2User.java (93%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/dto/KaKaoResponse.java (96%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/dto/OAuth2Response.java (91%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/jwt/JwtAuthorizationFilter.java (93%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/jwt/JwtUtil.java (98%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/oauth2/CustomOAuth2UserService.java (85%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/oauth2/OAuth2LoginSuccessHandler.java (91%) rename {security-front/src/main/java/com/nowait/auth => external-oauth/src/main/java/com/nowait/externaloauth}/service/CustomUserDetailService.java (83%) rename infra-aws/src/main/java/com/{example/infrastorage => nowait/infraaws}/config/AwsS3Config.java (95%) rename infra-aws/src/main/java/com/{example/infrastorage => nowait/infraaws}/s3/S3Service.java (98%) delete mode 100644 security-front/src/main/java/com/nowait/exception/ErrorMessage.java delete mode 100644 security-front/src/main/java/com/nowait/exception/ErrorResponse.java delete mode 100644 security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java rename security-front/src/main/java/com/nowait/{ => frontsecurity}/exception/RefreshTokenNotFoundException.java (65%) rename security-front/src/main/java/com/nowait/{ => frontsecurity}/exception/ResourceNotFoundException.java (77%) rename security-front/src/main/java/com/nowait/{ => frontsecurity}/exception/TokenBadRequestException.java (52%) rename security-front/src/main/java/com/nowait/{ => frontsecurity}/exception/UnauthorizedException.java (82%) diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java b/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java deleted file mode 100644 index 6b230d4b..00000000 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.apiadmin.store.service; - -import com.example.apiadmin.store.dto.StoreCreateRequest; -import com.example.apiadmin.store.dto.StoreCreateResponse; -import com.example.apiadmin.store.dto.StoreReadDto; -import com.example.apiadmin.store.dto.StoreUpdateRequest; - -public interface StoreService { - - StoreCreateResponse createStore(StoreCreateRequest request); - - StoreReadDto getStoreByStoreId(Long storeId); - - StoreReadDto updateStore(Long storeId, StoreUpdateRequest request); - - String deleteStore(Long storeId); - - Boolean toggleActive(Long storeId); -} diff --git a/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java b/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java deleted file mode 100644 index fcaf441e..00000000 --- a/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.apiuser; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@EnableJpaAuditing -@SpringBootApplication(scanBasePackages = { - "com.example.apiuser", - "com.nowait.auth" -}) -@EntityScan(basePackages = { - "com.example.menu.entity", // domain-menu - "com.example.domainstore.entity", // domain-store - "com.example.domaintoken.entity", - "com.nowaiting.user.entity", - "com.nowait.domainbookmark.entity", - "com.nowait.domainreservation.entity", - "com.nowait.domainorder.entity", - "com.nowait.domainorder.entity" -}) -@EnableJpaRepositories(basePackages = { - "com.example.menu.repository", - "com.nowaiting.user.repository", - "com.example.domainstore.repository", - "com.example.domaintoken.repository", - "com.nowait.domainbookmark.repository", - "com.nowait.domainorder.repository", - "com.nowait.domainreservation.repository" -}) -public class ApiUserApplication { - - public static void main(String[] args) { - SpringApplication.run(ApiUserApplication.class, args); - } - -} diff --git a/api-admin/.gitattributes b/application-admin/.gitattributes similarity index 100% rename from api-admin/.gitattributes rename to application-admin/.gitattributes diff --git a/api-admin/.gitignore b/application-admin/.gitignore similarity index 100% rename from api-admin/.gitignore rename to application-admin/.gitignore diff --git a/api-admin/build.gradle b/application-admin/build.gradle similarity index 100% rename from api-admin/build.gradle rename to application-admin/build.gradle diff --git a/api-admin/src/main/java/com/example/apiadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java similarity index 51% rename from api-admin/src/main/java/com/example/apiadmin/ApiAdminApplication.java rename to application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 29363011..5f43869a 100644 --- a/api-admin/src/main/java/com/example/apiadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -1,4 +1,4 @@ -package com.example.apiadmin; +package com.nowait.applicationadmin; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; @@ -7,27 +7,26 @@ @EnableJpaAuditing @SpringBootApplication(scanBasePackages = { - "com.example.apiadmin", - "com.example.infrastorage", + "com.nowait.applicationadmin", + "com.nowait.infraaws", "com.nowait.adminsecurity" }) @EntityScan(basePackages = { - "com.example.menu.entity", // domain-menu - "com.example.domainstore.entity", // domain-store - "com.example.domaintoken.entity", - "com.nowaiting.user.entity", - "com.nowait.domainreservation.entity", - "com.nowait.domainorder.entity", - "com.nowait.domainorder.entity" + "com.nowait.menu.entity", + "com.nowait.store.entity", + "com.nowait.token.entity", + "com.nowait.user.entity", + "com.nowait.reservation.entity", + "com.nowait.order.entity" }) @EnableJpaRepositories(basePackages = { - "com.example.menu.repository", - "com.nowaiting.user.repository", - "com.example.domainstore.repository", - "com.example.domaintoken.repository", - "com.nowait.domainorder.repository", - "com.nowait.domainreservation.repository" + "com.nowait.menu.repository", + "com.nowait.user.repository", + "com.nowait.store.repository", + "com.nowait.token.repository", + "com.nowait.order.repository", + "com.nowait.reservation.repository" }) public class ApiAdminApplication { public static void main(String[] args) { diff --git a/api-admin/src/main/java/com/example/apiadmin/exception/GlobalExceptionHandler.java b/application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java similarity index 89% rename from api-admin/src/main/java/com/example/apiadmin/exception/GlobalExceptionHandler.java rename to application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 5c5c5c1d..2ccd5726 100644 --- a/api-admin/src/main/java/com/example/apiadmin/exception/GlobalExceptionHandler.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.exception; +package com.nowait.applicationadmin.exception; -import static com.nowaiting.common.exception.ErrorMessage.*; +import static com.nowait.common.exception.ErrorMessage.*; import static org.springframework.http.HttpStatus.UNAUTHORIZED; import static org.springframework.http.HttpStatus.*; @@ -17,16 +17,16 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartException; -import com.example.apiadmin.reservation.exception.ReservationNotFoundException; -import com.example.domaintoken.exception.BusinessException; +import com.nowait.applicationadmin.reservation.exception.ReservationNotFoundException; +import com.nowait.token.exception.BusinessException; import com.nowait.adminsecurity.exception.ResourceNotFoundException; import com.nowait.adminsecurity.exception.UnauthorizedException; -import com.nowait.domainorder.exception.DuplicateOrderException; -import com.nowait.domainorder.exception.OrderItemsEmptyException; -import com.nowait.domainorder.exception.OrderParameterEmptyException; -import com.nowaiting.common.exception.ErrorMessage; -import com.nowaiting.common.exception.ErrorResponse; -import com.nowaiting.user.exception.UserNotFoundException; +import com.nowait.order.exception.DuplicateOrderException; +import com.nowait.order.exception.OrderItemsEmptyException; +import com.nowait.order.exception.OrderParameterEmptyException; +import com.nowait.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorResponse; +import com.nowait.user.exception.UserNotFoundException; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java index bc709367..f93eb255 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.menu.controller; +package com.nowait.applicationadmin.menu.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -11,11 +11,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiadmin.menu.dto.MenuCreateRequest; -import com.example.apiadmin.menu.dto.MenuCreateResponse; -import com.example.apiadmin.menu.dto.MenuUpdateRequest; -import com.example.apiadmin.menu.service.MenuService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationadmin.menu.dto.MenuCreateRequest; +import com.nowait.applicationadmin.menu.dto.MenuCreateResponse; +import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; +import com.nowait.applicationadmin.menu.service.MenuService; +import com.nowait.common.api.ApiUtils; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuImageController.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuImageController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java index a64d8bca..286b3915 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/controller/MenuImageController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.menu.controller; +package com.nowait.applicationadmin.menu.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -10,9 +10,9 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.example.apiadmin.menu.dto.MenuImageUploadResponse; -import com.example.apiadmin.menu.service.MenuImageService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationadmin.menu.dto.MenuImageUploadResponse; +import com.nowait.applicationadmin.menu.service.MenuImageService; +import com.nowait.common.api.ApiUtils; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java index 3dc462d2..7ac4bfe8 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateRequest.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java @@ -1,7 +1,7 @@ -package com.example.apiadmin.menu.dto; +package com.nowait.applicationadmin.menu.dto; -import com.example.menu.entity.Menu; +import com.nowait.menu.entity.Menu; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java similarity index 90% rename from api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java index c2d59e7f..35700684 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuCreateResponse.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java @@ -1,8 +1,8 @@ -package com.example.apiadmin.menu.dto; +package com.nowait.applicationadmin.menu.dto; import java.time.LocalDateTime; -import com.example.menu.entity.Menu; +import com.nowait.menu.entity.Menu; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuImageUploadResponse.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuImageUploadResponse.java similarity index 80% rename from api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuImageUploadResponse.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuImageUploadResponse.java index 44db28d4..b73755ed 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuImageUploadResponse.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuImageUploadResponse.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.menu.dto; +package com.nowait.applicationadmin.menu.dto; -import com.example.menu.entity.MenuImage; +import com.nowait.menu.entity.MenuImage; import lombok.Builder; import lombok.Getter; diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java similarity index 90% rename from api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java index 1206b870..c4e89b08 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java @@ -1,8 +1,8 @@ -package com.example.apiuser.menu.dto; +package com.nowait.applicationadmin.menu.dto; import java.util.List; -import com.example.menu.entity.Menu; +import com.nowait.menu.entity.Menu; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadResponse.java similarity index 88% rename from api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadResponse.java index 755b243f..61c206e1 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadResponse.java @@ -1,4 +1,4 @@ -package com.example.apiuser.menu.dto; +package com.nowait.applicationadmin.menu.dto; import java.util.List; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java similarity index 85% rename from api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java index bdee0e15..3f2878aa 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuUpdateRequest.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.menu.dto; +package com.nowait.applicationadmin.menu.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuImageService.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java similarity index 79% rename from api-admin/src/main/java/com/example/apiadmin/menu/service/MenuImageService.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java index 5aec2e7a..bc884793 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuImageService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java @@ -1,15 +1,15 @@ -package com.example.apiadmin.menu.service; +package com.nowait.applicationadmin.menu.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import com.example.apiadmin.menu.dto.MenuImageUploadResponse; -import com.example.infrastorage.s3.S3Service; -import com.example.menu.entity.Menu; -import com.example.menu.entity.MenuImage; -import com.example.menu.repository.MenuImageRepository; -import com.example.menu.repository.MenuRepository; +import com.nowait.applicationadmin.menu.dto.MenuImageUploadResponse; +import com.nowait.infraaws.s3.S3Service; +import com.nowait.menu.entity.Menu; +import com.nowait.menu.entity.MenuImage; +import com.nowait.menu.repository.MenuImageRepository; +import com.nowait.menu.repository.MenuRepository; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java similarity index 82% rename from api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java rename to application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index cf8fde2d..d5afc825 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/service/MenuService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -1,20 +1,20 @@ -package com.example.apiadmin.menu.service; +package com.nowait.applicationadmin.menu.service; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiadmin.menu.dto.MenuCreateRequest; -import com.example.apiadmin.menu.dto.MenuCreateResponse; -import com.example.apiadmin.menu.dto.MenuImageUploadResponse; -import com.example.apiadmin.menu.dto.MenuReadDto; -import com.example.apiadmin.menu.dto.MenuReadResponse; -import com.example.apiadmin.menu.dto.MenuUpdateRequest; -import com.example.menu.entity.Menu; -import com.example.menu.entity.MenuImage; -import com.example.menu.repository.MenuImageRepository; -import com.example.menu.repository.MenuRepository; +import com.nowait.applicationadmin.menu.dto.MenuCreateRequest; +import com.nowait.applicationadmin.menu.dto.MenuCreateResponse; +import com.nowait.applicationadmin.menu.dto.MenuImageUploadResponse; +import com.nowait.applicationadmin.menu.dto.MenuReadDto; +import com.nowait.applicationadmin.menu.dto.MenuReadResponse; +import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; +import com.nowait.menu.entity.Menu; +import com.nowait.menu.entity.MenuImage; +import com.nowait.menu.repository.MenuImageRepository; +import com.nowait.menu.repository.MenuRepository; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/order/controller/OrderController.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/order/controller/OrderController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 69c7e3f8..2adb243b 100644 --- a/api-admin/src/main/java/com/example/apiadmin/order/controller/OrderController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.order.controller; +package com.nowait.applicationadmin.order.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/controller/ReservationController.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java similarity index 83% rename from api-admin/src/main/java/com/example/apiadmin/reservation/controller/ReservationController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index db32a09f..b29d3b56 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/controller/ReservationController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.reservation.controller; +package com.nowait.applicationadmin.reservation.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -9,11 +9,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiadmin.reservation.dto.CallGetResponseDto; -import com.example.apiadmin.reservation.dto.ReservationStatusSummaryDto; -import com.example.apiadmin.reservation.dto.ReservationStatusUpdateRequestDto; -import com.example.apiadmin.reservation.service.ReservationService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; +import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; +import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; +import com.nowait.applicationadmin.reservation.service.ReservationService; +import com.nowait.common.api.ApiUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/CallGetResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java similarity index 86% rename from api-admin/src/main/java/com/example/apiadmin/reservation/dto/CallGetResponseDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java index 927638ee..6d366830 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/CallGetResponseDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java @@ -1,8 +1,8 @@ -package com.example.apiadmin.reservation.dto; +package com.nowait.applicationadmin.reservation.dto; import java.time.LocalDateTime; -import com.nowait.domainreservation.entity.Reservation; +import com.nowait.reservation.entity.Reservation; import lombok.Builder; import lombok.Getter; diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationGetResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationGetResponseDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java index 3f7aca3d..00022384 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationGetResponseDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java @@ -1,8 +1,8 @@ -package com.example.apiadmin.reservation.dto; +package com.nowait.applicationadmin.reservation.dto; import java.time.LocalDateTime; -import com.nowait.domainreservation.entity.Reservation; +import com.nowait.reservation.entity.Reservation; import lombok.Builder; import lombok.Getter; diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationStatusSummaryDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusSummaryDto.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationStatusSummaryDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusSummaryDto.java index 3cd9d406..7c49552b 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationStatusSummaryDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusSummaryDto.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.reservation.dto; +package com.nowait.applicationadmin.reservation.dto; import java.util.List; diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationStatusUpdateRequestDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusUpdateRequestDto.java similarity index 59% rename from api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationStatusUpdateRequestDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusUpdateRequestDto.java index 688e4c7f..161eed90 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/dto/ReservationStatusUpdateRequestDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusUpdateRequestDto.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.reservation.dto; +package com.nowait.applicationadmin.reservation.dto; -import com.nowaiting.common.enums.ReservationStatus; +import com.nowait.common.enums.ReservationStatus; import lombok.Builder; import lombok.Getter; diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/exception/ReservationNotFoundException.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java similarity index 61% rename from api-admin/src/main/java/com/example/apiadmin/reservation/exception/ReservationNotFoundException.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java index 0f9c37ad..491cc3f6 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/exception/ReservationNotFoundException.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.reservation.exception; +package com.nowait.applicationadmin.reservation.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class ReservationNotFoundException extends RuntimeException { public ReservationNotFoundException() { diff --git a/api-admin/src/main/java/com/example/apiadmin/reservation/service/ReservationService.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java similarity index 73% rename from api-admin/src/main/java/com/example/apiadmin/reservation/service/ReservationService.java rename to application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index e0bfbfd5..ee9831e9 100644 --- a/api-admin/src/main/java/com/example/apiadmin/reservation/service/ReservationService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.reservation.service; +package com.nowait.applicationadmin.reservation.service; import java.util.ArrayList; import java.util.List; @@ -6,15 +6,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiadmin.reservation.dto.CallGetResponseDto; -import com.example.apiadmin.reservation.dto.ReservationGetResponseDto; -import com.example.apiadmin.reservation.dto.ReservationStatusSummaryDto; -import com.example.apiadmin.reservation.dto.ReservationStatusUpdateRequestDto; -import com.example.apiadmin.reservation.exception.ReservationNotFoundException; -import com.example.domainstore.repository.StoreRepository; -import com.nowait.domainreservation.entity.Reservation; -import com.nowait.domainreservation.repository.ReservationRepository; -import com.nowaiting.common.enums.ReservationStatus; +import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; +import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; +import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; +import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; +import com.nowait.applicationadmin.reservation.exception.ReservationNotFoundException; +import com.nowait.reservation.entity.Reservation; +import com.nowait.reservation.repository.ReservationRepository; +import com.nowait.common.enums.ReservationStatus; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java similarity index 85% rename from api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java index fac4a429..f9ee3a27 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.store.controller; +package com.nowait.applicationadmin.store.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -11,11 +11,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiadmin.store.dto.StoreCreateRequest; -import com.example.apiadmin.store.dto.StoreCreateResponse; -import com.example.apiadmin.store.dto.StoreUpdateRequest; -import com.example.apiadmin.store.service.StoreService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationadmin.store.dto.StoreCreateRequest; +import com.nowait.applicationadmin.store.dto.StoreCreateResponse; +import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; +import com.nowait.applicationadmin.store.service.StoreService; +import com.nowait.common.api.ApiUtils; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java similarity index 89% rename from api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java index 61bfbb34..b3547d03 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/controller/StoreImageController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.store.controller; +package com.nowait.applicationadmin.store.controller; import java.util.List; @@ -12,9 +12,9 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.example.apiadmin.store.dto.StoreImageUploadResponse; -import com.example.apiadmin.store.service.StoreImageService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationadmin.store.dto.StoreImageUploadResponse; +import com.nowait.applicationadmin.store.service.StoreImageService; +import com.nowait.common.api.ApiUtils; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreCreateRequest.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/store/dto/StoreCreateRequest.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java index 5fa790f0..fb4f58a4 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreCreateRequest.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.store.dto; +package com.nowait.applicationadmin.store.dto; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreCreateResponse.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java similarity index 90% rename from api-admin/src/main/java/com/example/apiadmin/store/dto/StoreCreateResponse.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java index f1c359ec..2356d469 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreCreateResponse.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java @@ -1,8 +1,8 @@ -package com.example.apiadmin.store.dto; +package com.nowait.applicationadmin.store.dto; import java.time.LocalDateTime; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java similarity index 80% rename from api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java index bbc1d9da..9e2abe7d 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreImageUploadResponse.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.store.dto; +package com.nowait.applicationadmin.store.dto; -import com.example.domainstore.entity.StoreImage; +import com.nowait.store.entity.StoreImage; import lombok.Builder; import lombok.Getter; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java similarity index 91% rename from api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java index 0a0148c8..00e1a234 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreReadDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java @@ -1,9 +1,9 @@ -package com.example.apiadmin.store.dto; +package com.nowait.applicationadmin.store.dto; import java.time.LocalDateTime; import java.util.List; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreUpdateRequest.java similarity index 85% rename from api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreUpdateRequest.java index bbe0dfdc..c6b2ccbb 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/dto/StoreUpdateRequest.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/dto/StoreUpdateRequest.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.store.dto; +package com.nowait.applicationadmin.store.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java similarity index 85% rename from api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java index 1c7bafb4..f0b5e833 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreImageService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.store.service; +package com.nowait.applicationadmin.store.service; import java.util.ArrayList; import java.util.List; @@ -8,13 +8,13 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import com.example.apiadmin.store.dto.StoreImageUploadResponse; -import com.example.domainstore.entity.Store; +import com.nowait.applicationadmin.store.dto.StoreImageUploadResponse; +import com.nowait.store.entity.Store; -import com.example.domainstore.entity.StoreImage; -import com.example.domainstore.repository.StoreImageRepository; -import com.example.domainstore.repository.StoreRepository; -import com.example.infrastorage.s3.S3Service; +import com.nowait.store.entity.StoreImage; +import com.nowait.store.repository.StoreImageRepository; +import com.nowait.store.repository.StoreRepository; +import com.nowait.infraaws.s3.S3Service; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java new file mode 100644 index 00000000..02c39915 --- /dev/null +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java @@ -0,0 +1,19 @@ +package com.nowait.applicationadmin.store.service; + +import com.nowait.applicationadmin.store.dto.StoreCreateRequest; +import com.nowait.applicationadmin.store.dto.StoreCreateResponse; +import com.nowait.applicationadmin.store.dto.StoreReadDto; +import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; + +public interface StoreService { + + StoreCreateResponse createStore(StoreCreateRequest request); + + StoreReadDto getStoreByStoreId(Long storeId); + + StoreReadDto updateStore(Long storeId, StoreUpdateRequest request); + + String deleteStore(Long storeId); + + Boolean toggleActive(Long storeId); +} diff --git a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java similarity index 81% rename from api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java rename to application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index ee7be676..56b6406b 100644 --- a/api-admin/src/main/java/com/example/apiadmin/store/service/StoreServiceImpl.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -1,19 +1,19 @@ -package com.example.apiadmin.store.service; +package com.nowait.applicationadmin.store.service; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiadmin.store.dto.StoreCreateRequest; -import com.example.apiadmin.store.dto.StoreCreateResponse; -import com.example.apiadmin.store.dto.StoreImageUploadResponse; -import com.example.apiadmin.store.dto.StoreReadDto; -import com.example.apiadmin.store.dto.StoreUpdateRequest; -import com.example.domainstore.entity.Store; -import com.example.domainstore.entity.StoreImage; -import com.example.domainstore.repository.StoreImageRepository; -import com.example.domainstore.repository.StoreRepository; +import com.nowait.applicationadmin.store.dto.StoreCreateRequest; +import com.nowait.applicationadmin.store.dto.StoreCreateResponse; +import com.nowait.applicationadmin.store.dto.StoreImageUploadResponse; +import com.nowait.applicationadmin.store.dto.StoreReadDto; +import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; +import com.nowait.store.entity.Store; +import com.nowait.store.entity.StoreImage; +import com.nowait.store.repository.StoreImageRepository; +import com.nowait.store.repository.StoreRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/token/controller/TokenController.java b/application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java similarity index 89% rename from api-admin/src/main/java/com/example/apiadmin/token/controller/TokenController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java index 0ad4e64f..ebadba02 100644 --- a/api-admin/src/main/java/com/example/apiadmin/token/controller/TokenController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.token.controller; +package com.nowait.applicationadmin.token.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -8,9 +8,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiadmin.token.dto.AuthenticationResponse; -import com.example.apiadmin.token.dto.RefreshTokenRequest; -import com.example.apiadmin.token.service.TokenService; +import com.nowait.applicationadmin.token.dto.AuthenticationResponse; +import com.nowait.applicationadmin.token.dto.RefreshTokenRequest; +import com.nowait.applicationadmin.token.service.TokenService; import com.nowait.adminsecurity.auth.jwt.JwtUtil; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java b/application-admin/src/main/java/com/nowait/applicationadmin/token/dto/AuthenticationResponse.java similarity index 90% rename from api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java rename to application-admin/src/main/java/com/nowait/applicationadmin/token/dto/AuthenticationResponse.java index 71db8914..cb67851d 100644 --- a/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/token/dto/AuthenticationResponse.java @@ -1,4 +1,4 @@ -package com.example.apiuser.token.dto; +package com.nowait.applicationadmin.token.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java b/application-admin/src/main/java/com/nowait/applicationadmin/token/dto/RefreshTokenRequest.java similarity index 84% rename from api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java rename to application-admin/src/main/java/com/nowait/applicationadmin/token/dto/RefreshTokenRequest.java index 4ab8ae75..df7ed26f 100644 --- a/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/token/dto/RefreshTokenRequest.java @@ -1,4 +1,4 @@ -package com.example.apiuser.token.dto; +package com.nowait.applicationadmin.token.dto; import jakarta.validation.constraints.NotBlank; import lombok.Getter; diff --git a/api-admin/src/main/java/com/example/apiadmin/token/service/TokenService.java b/application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java similarity index 93% rename from api-admin/src/main/java/com/example/apiadmin/token/service/TokenService.java rename to application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java index 458ec8e2..3ba54b63 100644 --- a/api-admin/src/main/java/com/example/apiadmin/token/service/TokenService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.token.service; +package com.nowait.applicationadmin.token.service; import java.time.LocalDateTime; import java.util.Optional; @@ -6,8 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.domaintoken.entity.Token; -import com.example.domaintoken.repository.TokenRepository; +import com.nowait.token.entity.Token; +import com.nowait.token.repository.TokenRepository; import com.nowait.adminsecurity.auth.jwt.JwtUtil; import com.nowait.adminsecurity.exception.RefreshTokenNotFoundException; import com.nowait.adminsecurity.exception.TokenBadRequestException; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/controller/UserController.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/controller/UserController.java similarity index 86% rename from api-admin/src/main/java/com/example/apiadmin/user/controller/UserController.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/controller/UserController.java index 412e5a99..be794e84 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/controller/UserController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/controller/UserController.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.user.controller; +package com.nowait.applicationadmin.user.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -7,10 +7,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiadmin.user.dto.ManagerLoginRequestDto; -import com.example.apiadmin.user.dto.ManagerSignupRequestDto; -import com.example.apiadmin.user.serivce.UserService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationadmin.user.dto.ManagerLoginRequestDto; +import com.nowait.applicationadmin.user.dto.ManagerSignupRequestDto; +import com.nowait.applicationadmin.user.serivce.UserService; +import com.nowait.common.api.ApiUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerLoginRequestDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginRequestDto.java similarity index 94% rename from api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerLoginRequestDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginRequestDto.java index d1f40771..e545bf6d 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerLoginRequestDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginRequestDto.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.user.dto; +package com.nowait.applicationadmin.user.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerLoginResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java similarity index 92% rename from api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerLoginResponseDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java index 4a36fb27..48acf335 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerLoginResponseDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java @@ -1,6 +1,6 @@ -package com.example.apiadmin.user.dto; +package com.nowait.applicationadmin.user.dto; -import com.nowaiting.user.entity.User; +import com.nowait.user.entity.User; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerSignupRequestDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerSignupRequestDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java index 3d61b7ad..3d89c41a 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerSignupRequestDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java @@ -1,8 +1,8 @@ -package com.example.apiadmin.user.dto; +package com.nowait.applicationadmin.user.dto; -import com.nowaiting.common.enums.Role; -import com.nowaiting.common.enums.SocialType; -import com.nowaiting.user.entity.User; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.user.entity.User; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerSignupResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupResponseDto.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerSignupResponseDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupResponseDto.java index 9d311942..46e8a1e4 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/dto/ManagerSignupResponseDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupResponseDto.java @@ -1,7 +1,7 @@ -package com.example.apiadmin.user.dto; +package com.nowait.applicationadmin.user.dto; -import com.nowaiting.common.enums.Role; -import com.nowaiting.user.entity.User; +import com.nowait.common.enums.Role; +import com.nowait.user.entity.User; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/dto/UserResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/UserResponseDto.java similarity index 87% rename from api-admin/src/main/java/com/example/apiadmin/user/dto/UserResponseDto.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/dto/UserResponseDto.java index bbcdb8ea..d00601f2 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/dto/UserResponseDto.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/dto/UserResponseDto.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.user.dto; +package com.nowait.applicationadmin.user.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/api-admin/src/main/java/com/example/apiadmin/user/serivce/UserService.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java similarity index 83% rename from api-admin/src/main/java/com/example/apiadmin/user/serivce/UserService.java rename to application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java index 1e026c56..9a11c497 100644 --- a/api-admin/src/main/java/com/example/apiadmin/user/serivce/UserService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.user.serivce; +package com.nowait.applicationadmin.user.serivce; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.AuthenticationProvider; @@ -8,14 +8,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiadmin.user.dto.ManagerLoginRequestDto; -import com.example.apiadmin.user.dto.ManagerLoginResponseDto; -import com.example.apiadmin.user.dto.ManagerSignupRequestDto; -import com.example.apiadmin.user.dto.ManagerSignupResponseDto; +import com.nowait.applicationadmin.user.dto.ManagerLoginRequestDto; +import com.nowait.applicationadmin.user.dto.ManagerLoginResponseDto; +import com.nowait.applicationadmin.user.dto.ManagerSignupRequestDto; +import com.nowait.applicationadmin.user.dto.ManagerSignupResponseDto; import com.nowait.adminsecurity.auth.jwt.JwtUtil; -import com.nowaiting.user.entity.MemberDetails; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.user.entity.MemberDetails; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java b/application-config/src/main/java/com/nowait/config/config/AsyncConfig.java similarity index 92% rename from application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java rename to application-config/src/main/java/com/nowait/config/config/AsyncConfig.java index 9c839fbb..0d63871f 100644 --- a/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java +++ b/application-config/src/main/java/com/nowait/config/config/AsyncConfig.java @@ -1,4 +1,4 @@ -package com.example.applicationconfig.config; +package com.nowait.config.config; import java.util.concurrent.Executor; diff --git a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java b/application-config/src/main/java/com/nowait/config/config/CorsConfig.java similarity index 96% rename from application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java rename to application-config/src/main/java/com/nowait/config/config/CorsConfig.java index 6d0a6770..06823454 100644 --- a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java +++ b/application-config/src/main/java/com/nowait/config/config/CorsConfig.java @@ -1,4 +1,4 @@ -package com.example.applicationconfig.config; +package com.nowait.config.config; import java.util.List; diff --git a/api-user/.gitattributes b/application-user/.gitattributes similarity index 100% rename from api-user/.gitattributes rename to application-user/.gitattributes diff --git a/api-user/.gitignore b/application-user/.gitignore similarity index 100% rename from api-user/.gitignore rename to application-user/.gitignore diff --git a/api-user/build.gradle b/application-user/build.gradle similarity index 95% rename from api-user/build.gradle rename to application-user/build.gradle index 74bdca87..6b68d7be 100644 --- a/api-user/build.gradle +++ b/application-user/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation project(':domain-reservation') // 예약 관련 도메인 implementation project(':infra-aws') // 사용자 관련 도메인 implementation project(':security-front') // 사용자 관련 도메인 + implementation project(':external-oauth') // 외부 OAuth 관련 도메인 // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/api-user/gradle/wrapper/gradle-wrapper.jar b/application-user/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from api-user/gradle/wrapper/gradle-wrapper.jar rename to application-user/gradle/wrapper/gradle-wrapper.jar diff --git a/api-user/gradle/wrapper/gradle-wrapper.properties b/application-user/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from api-user/gradle/wrapper/gradle-wrapper.properties rename to application-user/gradle/wrapper/gradle-wrapper.properties diff --git a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java new file mode 100644 index 00000000..7e59d07d --- /dev/null +++ b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java @@ -0,0 +1,40 @@ +package com.nowait.applicationuser; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@EnableJpaAuditing +@SpringBootApplication(scanBasePackages = { + "com.nowait.applicationuser", + "com.nowait.frontsecurity", + "com.nowait.externaloauth" +}) +@EntityScan(basePackages = { + "com.nowait.menu.entity", + "com.nowait.store.entity", + "com.nowait.token.entity", + "com.nowait.user.entity", + "com.nowait.bookmark.entity", + "com.nowait.reservation.entity", + "com.nowait.order.entity", + "com.nowait.order.entity" +}) +@EnableJpaRepositories(basePackages = { + "com.nowait.menu.repository", + "com.nowait.user.repository", + "com.nowait.store.repository", + "com.nowait.token.repository", + "com.nowait.bookmark.repository", + "com.nowait.order.repository", + "com.nowait.reservation.repository" +}) +public class ApiUserApplication { + + public static void main(String[] args) { + SpringApplication.run(ApiUserApplication.class, args); + } + +} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java b/application-user/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java similarity index 89% rename from api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java rename to application-user/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java index 32c4132a..14d47d7e 100644 --- a/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java @@ -1,4 +1,4 @@ -package com.example.apiuser.bookmark.controller; +package com.nowait.applicationuser.bookmark.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -10,10 +10,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiuser.bookmark.dto.BookmarkCreateResponse; -import com.example.apiuser.bookmark.service.BookmarkService; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationuser.bookmark.dto.BookmarkCreateResponse; +import com.nowait.applicationuser.bookmark.service.BookmarkService; +import com.nowait.common.api.ApiUtils; +import com.nowait.externaloauth.dto.CustomOAuth2User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java b/application-user/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkCreateResponse.java similarity index 83% rename from api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkCreateResponse.java index 5e4a8f65..3f0d03e2 100644 --- a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkCreateResponse.java @@ -1,6 +1,6 @@ -package com.example.apiuser.bookmark.dto; +package com.nowait.applicationuser.bookmark.dto; -import com.nowait.domainbookmark.entity.Bookmark; +import com.nowait.bookmark.entity.Bookmark; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java b/application-user/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkGetResponse.java similarity index 83% rename from api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkGetResponse.java index 6f05db8e..8caf5c55 100644 --- a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkGetResponse.java @@ -1,6 +1,6 @@ -package com.example.apiuser.bookmark.dto; +package com.nowait.applicationuser.bookmark.dto; -import com.nowait.domainbookmark.entity.Bookmark; +import com.nowait.bookmark.entity.Bookmark; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java b/application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java similarity index 82% rename from api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java rename to application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 91ce0ff8..5aae6553 100644 --- a/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -1,19 +1,19 @@ -package com.example.apiuser.bookmark.service; +package com.nowait.applicationuser.bookmark.service; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiuser.bookmark.dto.BookmarkCreateResponse; -import com.example.apiuser.bookmark.dto.BookmarkGetResponse; -import com.example.domainstore.entity.Store; -import com.example.domainstore.repository.StoreRepository; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.domainbookmark.entity.Bookmark; -import com.nowait.domainbookmark.repository.BookmarkRepository; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.applicationuser.bookmark.dto.BookmarkCreateResponse; +import com.nowait.applicationuser.bookmark.dto.BookmarkGetResponse; +import com.nowait.externaloauth.dto.CustomOAuth2User; +import com.nowait.store.entity.Store; +import com.nowait.store.repository.StoreRepository; +import com.nowait.bookmark.entity.Bookmark; +import com.nowait.bookmark.repository.BookmarkRepository; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java b/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java similarity index 88% rename from api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java rename to application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index fedb029f..bcf4eda3 100644 --- a/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java +++ b/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ -package com.example.apiuser.exception; +package com.nowait.applicationuser.exception; -import static com.nowaiting.common.exception.ErrorMessage.*; +import static com.nowait.common.exception.ErrorMessage.*; import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.UNAUTHORIZED; @@ -18,18 +18,18 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartException; -import com.example.apiuser.reservation.exception.ReservationNotFoundException; -import com.nowait.domainbookmark.exception.BookmarkOwnerMismatchException; -import com.nowait.domainbookmark.exception.DuplicateBookmarkException; -import com.nowait.domainorder.exception.DuplicateOrderException; -import com.nowait.domainorder.exception.OrderItemsEmptyException; -import com.nowait.domainorder.exception.OrderParameterEmptyException; -import com.nowait.exception.BusinessException; -import com.nowait.exception.ResourceNotFoundException; -import com.nowait.exception.UnauthorizedException; -import com.nowaiting.common.exception.ErrorMessage; -import com.nowaiting.common.exception.ErrorResponse; -import com.nowaiting.user.exception.UserNotFoundException; +import com.nowait.applicationuser.reservation.exception.ReservationNotFoundException; +import com.nowait.bookmark.exception.BookmarkOwnerMismatchException; +import com.nowait.bookmark.exception.DuplicateBookmarkException; +import com.nowait.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorResponse; +import com.nowait.frontsecurity.exception.ResourceNotFoundException; +import com.nowait.frontsecurity.exception.UnauthorizedException; +import com.nowait.order.exception.DuplicateOrderException; +import com.nowait.order.exception.OrderItemsEmptyException; +import com.nowait.order.exception.OrderParameterEmptyException; +import com.nowait.token.exception.BusinessException; +import com.nowait.user.exception.UserNotFoundException; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; diff --git a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java b/application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java similarity index 87% rename from api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java rename to application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java index d1270ccb..f7627f2e 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java @@ -1,4 +1,4 @@ -package com.example.apiuser.menu.controller; +package com.nowait.applicationuser.menu.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiuser.menu.service.MenuService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationuser.menu.service.MenuService; +import com.nowait.common.api.ApiUtils; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java b/application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuImageUploadResponse.java similarity index 80% rename from api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuImageUploadResponse.java index c32be356..b7e24a85 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuImageUploadResponse.java @@ -1,6 +1,6 @@ -package com.example.apiuser.menu.dto; +package com.nowait.applicationuser.menu.dto; -import com.example.menu.entity.MenuImage; +import com.nowait.menu.entity.MenuImage; import lombok.Builder; import lombok.Getter; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java b/application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java similarity index 90% rename from api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java rename to application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java index c369f0fc..e0fcad93 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java @@ -1,8 +1,8 @@ -package com.example.apiadmin.menu.dto; +package com.nowait.applicationuser.menu.dto; import java.util.List; -import com.example.menu.entity.Menu; +import com.nowait.menu.entity.Menu; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadResponse.java b/application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java similarity index 89% rename from api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java index 5df79e2f..5e9c6c21 100644 --- a/api-admin/src/main/java/com/example/apiadmin/menu/dto/MenuReadResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.menu.dto; +package com.nowait.applicationuser.menu.dto; import java.util.List; diff --git a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java b/application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java similarity index 77% rename from api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java rename to application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index 8763f270..bd28fada 100644 --- a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -1,17 +1,17 @@ -package com.example.apiuser.menu.service; +package com.nowait.applicationuser.menu.service; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiuser.menu.dto.MenuImageUploadResponse; -import com.example.apiuser.menu.dto.MenuReadDto; -import com.example.apiuser.menu.dto.MenuReadResponse; -import com.example.menu.entity.Menu; -import com.example.menu.entity.MenuImage; -import com.example.menu.repository.MenuImageRepository; -import com.example.menu.repository.MenuRepository; +import com.nowait.applicationuser.menu.dto.MenuImageUploadResponse; +import com.nowait.applicationuser.menu.dto.MenuReadDto; +import com.nowait.applicationuser.menu.dto.MenuReadResponse; +import com.nowait.menu.entity.Menu; +import com.nowait.menu.entity.MenuImage; +import com.nowait.menu.repository.MenuImageRepository; +import com.nowait.menu.repository.MenuRepository; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java b/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java similarity index 86% rename from api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java rename to application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 65238120..87cb3fe0 100644 --- a/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -1,4 +1,4 @@ -package com.example.apiuser.order.controller; +package com.nowait.applicationuser.order.controller; import java.util.List; @@ -11,11 +11,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiuser.order.dto.OrderCreateRequestDto; -import com.example.apiuser.order.dto.OrderCreateResponseDto; -import com.example.apiuser.order.dto.OrderItemListGetResponseDto; -import com.example.apiuser.order.service.OrderService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; +import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; +import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; +import com.nowait.applicationuser.order.service.OrderService; +import com.nowait.common.api.ApiUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/CartItemDto.java similarity index 79% rename from api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java rename to application-user/src/main/java/com/nowait/applicationuser/order/dto/CartItemDto.java index 1b8fc52b..72b7b58e 100644 --- a/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/CartItemDto.java @@ -1,4 +1,4 @@ -package com.example.apiuser.order.dto; +package com.nowait.applicationuser.order.dto; import lombok.Builder; import lombok.Getter; diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java similarity index 84% rename from api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java rename to application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java index 056d44a6..1372a980 100644 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java @@ -1,4 +1,4 @@ -package com.example.apiuser.order.dto; +package com.nowait.applicationuser.order.dto; import java.util.List; diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java similarity index 89% rename from api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java rename to application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index 1293ac0f..cd8f6568 100644 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -1,8 +1,8 @@ -package com.example.apiuser.order.dto; +package com.nowait.applicationuser.order.dto; import java.util.List; -import com.nowait.domainorder.entity.UserOrder; +import com.nowait.order.entity.UserOrder; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java similarity index 87% rename from api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java rename to application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java index 02e24e3f..ef6bb13e 100644 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java @@ -1,6 +1,6 @@ -package com.example.apiuser.order.dto; +package com.nowait.applicationuser.order.dto; -import com.nowait.domainorder.entity.OrderItem; +import com.nowait.order.entity.OrderItem; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java similarity index 88% rename from api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java rename to application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java index 12f04514..1c29a4f2 100644 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.apiuser.order.dto; +package com.nowait.applicationuser.order.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java similarity index 82% rename from api-user/src/main/java/com/example/apiuser/order/service/OrderService.java rename to application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 1644ba16..07cc19c3 100644 --- a/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -1,4 +1,4 @@ -package com.example.apiuser.order.service; +package com.nowait.applicationuser.order.service; import java.time.LocalDateTime; import java.util.List; @@ -11,21 +11,21 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.DigestUtils; -import com.example.apiuser.order.dto.CartItemDto; -import com.example.apiuser.order.dto.OrderCreateRequestDto; -import com.example.apiuser.order.dto.OrderCreateResponseDto; -import com.example.apiuser.order.dto.OrderItemListGetResponseDto; -import com.example.domainstore.entity.Store; -import com.example.domainstore.repository.StoreRepository; -import com.example.menu.entity.Menu; -import com.example.menu.repository.MenuRepository; -import com.nowait.domainorder.entity.OrderItem; -import com.nowait.domainorder.entity.UserOrder; -import com.nowait.domainorder.exception.DuplicateOrderException; -import com.nowait.domainorder.exception.OrderItemsEmptyException; -import com.nowait.domainorder.exception.OrderParameterEmptyException; -import com.nowait.domainorder.repository.OrderItemRepository; -import com.nowait.domainorder.repository.OrderRepository; +import com.nowait.applicationuser.order.dto.CartItemDto; +import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; +import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; +import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; +import com.nowait.store.entity.Store; +import com.nowait.store.repository.StoreRepository; +import com.nowait.menu.entity.Menu; +import com.nowait.menu.repository.MenuRepository; +import com.nowait.order.entity.OrderItem; +import com.nowait.order.entity.UserOrder; +import com.nowait.order.exception.DuplicateOrderException; +import com.nowait.order.exception.OrderItemsEmptyException; +import com.nowait.order.exception.OrderParameterEmptyException; +import com.nowait.order.repository.OrderItemRepository; +import com.nowait.order.repository.OrderRepository; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java b/application-user/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java similarity index 79% rename from api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java rename to application-user/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 75416513..dd3176e8 100644 --- a/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -1,4 +1,4 @@ -package com.example.apiuser.reservation.controller; +package com.nowait.applicationuser.reservation.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -9,11 +9,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiuser.reservation.dto.ReservationCreateRequestDto; -import com.example.apiuser.reservation.dto.ReservationCreateResponseDto; -import com.example.apiuser.reservation.service.ReservationService; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; +import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; +import com.nowait.applicationuser.reservation.service.ReservationService; +import com.nowait.common.api.ApiUtils; +import com.nowait.externaloauth.dto.CustomOAuth2User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateRequestDto.java similarity index 72% rename from api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java rename to application-user/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateRequestDto.java index 03fbfb4f..c6871832 100644 --- a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateRequestDto.java @@ -1,4 +1,4 @@ -package com.example.apiuser.reservation.dto; +package com.nowait.applicationuser.reservation.dto; import lombok.Builder; import lombok.Getter; diff --git a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java b/application-user/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateResponseDto.java similarity index 84% rename from api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java rename to application-user/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateResponseDto.java index fa581589..0a07e466 100644 --- a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateResponseDto.java @@ -1,4 +1,4 @@ -package com.example.apiuser.reservation.dto; +package com.nowait.applicationuser.reservation.dto; import java.time.LocalDateTime; diff --git a/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java b/application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java similarity index 61% rename from api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java rename to application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java index 37dc86eb..bb6db2f8 100644 --- a/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java +++ b/application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java @@ -1,6 +1,6 @@ -package com.example.apiuser.reservation.exception; +package com.nowait.applicationuser.reservation.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class ReservationNotFoundException extends RuntimeException { public ReservationNotFoundException() { diff --git a/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java b/application-user/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java similarity index 67% rename from api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java rename to application-user/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index a5394ec8..e4c3ed4d 100644 --- a/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -1,21 +1,21 @@ -package com.example.apiuser.reservation.service; +package com.nowait.applicationuser.reservation.service; import java.time.LocalDateTime; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiuser.reservation.dto.ReservationCreateRequestDto; -import com.example.apiuser.reservation.dto.ReservationCreateResponseDto; -import com.example.domainstore.entity.Store; -import com.example.domainstore.repository.StoreRepository; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.domainreservation.entity.Reservation; -import com.nowait.domainreservation.repository.ReservationRepository; -import com.nowaiting.common.enums.ReservationStatus; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.exception.UserNotFoundException; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; +import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; +import com.nowait.externaloauth.dto.CustomOAuth2User; +import com.nowait.store.entity.Store; +import com.nowait.store.repository.StoreRepository; +import com.nowait.reservation.entity.Reservation; +import com.nowait.reservation.repository.ReservationRepository; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.user.entity.User; +import com.nowait.user.exception.UserNotFoundException; +import com.nowait.user.repository.UserRepository; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java b/application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java similarity index 90% rename from api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java rename to application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index 6a3cba2e..f3f7f2c1 100644 --- a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -1,4 +1,4 @@ -package com.example.apiuser.store.controller; +package com.nowait.applicationuser.store.controller; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -9,8 +9,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.example.apiuser.store.service.StoreService; -import com.nowaiting.common.api.ApiUtils; +import com.nowait.applicationuser.store.service.StoreService; +import com.nowait.common.api.ApiUtils; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java b/application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java similarity index 80% rename from api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java index 23a1b224..8f5612d0 100644 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java @@ -1,6 +1,6 @@ -package com.example.apiuser.store.dto; +package com.nowait.applicationuser.store.dto; -import com.example.domainstore.entity.StoreImage; +import com.nowait.store.entity.StoreImage; import lombok.Builder; import lombok.Getter; diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java b/application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreReadDto.java similarity index 91% rename from api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java rename to application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreReadDto.java index d6cfab6e..b13cb220 100644 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreReadDto.java @@ -1,9 +1,9 @@ -package com.example.apiuser.store.dto; +package com.nowait.applicationuser.store.dto; import java.time.LocalDateTime; import java.util.List; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java b/application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreReadResponse.java similarity index 90% rename from api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreReadResponse.java index a580e562..37faef23 100644 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/dto/StoreReadResponse.java @@ -1,4 +1,4 @@ -package com.example.apiuser.store.dto; +package com.nowait.applicationuser.store.dto; import java.util.List; diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java similarity index 64% rename from api-user/src/main/java/com/example/apiuser/store/service/StoreService.java rename to application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 83ea3467..e2f37f61 100644 --- a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -1,11 +1,11 @@ -package com.example.apiuser.store.service; +package com.nowait.applicationuser.store.service; import java.util.List; import org.springframework.data.domain.Pageable; -import com.example.apiuser.store.dto.StoreReadDto; -import com.example.apiuser.store.dto.StoreReadResponse; +import com.nowait.applicationuser.store.dto.StoreReadDto; +import com.nowait.applicationuser.store.dto.StoreReadResponse; public interface StoreService { diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java similarity index 86% rename from api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java rename to application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index c68657f9..2660af67 100644 --- a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.apiuser.store.service; +package com.nowait.applicationuser.store.service; import java.util.List; @@ -6,13 +6,13 @@ import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.apiuser.store.dto.StoreImageUploadResponse; -import com.example.apiuser.store.dto.StoreReadDto; -import com.example.apiuser.store.dto.StoreReadResponse; -import com.example.domainstore.entity.Store; -import com.example.domainstore.entity.StoreImage; -import com.example.domainstore.repository.StoreImageRepository; -import com.example.domainstore.repository.StoreRepository; +import com.nowait.applicationuser.store.dto.StoreImageUploadResponse; +import com.nowait.applicationuser.store.dto.StoreReadDto; +import com.nowait.applicationuser.store.dto.StoreReadResponse; +import com.nowait.store.entity.Store; +import com.nowait.store.entity.StoreImage; +import com.nowait.store.repository.StoreImageRepository; +import com.nowait.store.repository.StoreRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; diff --git a/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java b/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java similarity index 87% rename from api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java rename to application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index 11120f30..bb72e0ec 100644 --- a/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -1,4 +1,4 @@ -package com.example.apiuser.token.controller; +package com.nowait.applicationuser.token.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -8,10 +8,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.apiuser.token.dto.AuthenticationResponse; -import com.example.apiuser.token.dto.RefreshTokenRequest; -import com.example.apiuser.token.service.TokenService; -import com.nowait.auth.jwt.JwtUtil; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.dto.RefreshTokenRequest; +import com.nowait.applicationuser.token.service.TokenService; +import com.nowait.externaloauth.jwt.JwtUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/api-admin/src/main/java/com/example/apiadmin/token/dto/AuthenticationResponse.java b/application-user/src/main/java/com/nowait/applicationuser/token/dto/AuthenticationResponse.java similarity index 90% rename from api-admin/src/main/java/com/example/apiadmin/token/dto/AuthenticationResponse.java rename to application-user/src/main/java/com/nowait/applicationuser/token/dto/AuthenticationResponse.java index 170a3166..5a1a3c39 100644 --- a/api-admin/src/main/java/com/example/apiadmin/token/dto/AuthenticationResponse.java +++ b/application-user/src/main/java/com/nowait/applicationuser/token/dto/AuthenticationResponse.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.token.dto; +package com.nowait.applicationuser.token.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/api-admin/src/main/java/com/example/apiadmin/token/dto/RefreshTokenRequest.java b/application-user/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java similarity index 85% rename from api-admin/src/main/java/com/example/apiadmin/token/dto/RefreshTokenRequest.java rename to application-user/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java index aa97db17..c0bf4e68 100644 --- a/api-admin/src/main/java/com/example/apiadmin/token/dto/RefreshTokenRequest.java +++ b/application-user/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java @@ -1,4 +1,4 @@ -package com.example.apiadmin.token.dto; +package com.nowait.applicationuser.token.dto; import jakarta.validation.constraints.NotBlank; import lombok.Getter; diff --git a/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java b/application-user/src/main/java/com/nowait/applicationuser/token/service/TokenService.java similarity index 86% rename from api-user/src/main/java/com/example/apiuser/token/service/TokenService.java rename to application-user/src/main/java/com/nowait/applicationuser/token/service/TokenService.java index 56845238..a3acfee8 100644 --- a/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/token/service/TokenService.java @@ -1,4 +1,4 @@ -package com.example.apiuser.token.service; +package com.nowait.applicationuser.token.service; import java.time.LocalDateTime; import java.util.Optional; @@ -6,11 +6,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.domaintoken.entity.Token; -import com.example.domaintoken.repository.TokenRepository; -import com.nowait.auth.jwt.JwtUtil; -import com.nowait.exception.RefreshTokenNotFoundException; -import com.nowait.exception.TokenBadRequestException; +import com.nowait.externaloauth.jwt.JwtUtil; +import com.nowait.token.entity.Token; +import com.nowait.token.repository.TokenRepository; +import com.nowait.frontsecurity.exception.RefreshTokenNotFoundException; +import com.nowait.frontsecurity.exception.TokenBadRequestException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/common/src/main/java/com/nowaiting/common/api/ApiError.java b/common/src/main/java/com/nowait/common/api/ApiError.java similarity index 89% rename from common/src/main/java/com/nowaiting/common/api/ApiError.java rename to common/src/main/java/com/nowait/common/api/ApiError.java index b5cbca81..08f3e598 100644 --- a/common/src/main/java/com/nowaiting/common/api/ApiError.java +++ b/common/src/main/java/com/nowait/common/api/ApiError.java @@ -1,6 +1,6 @@ -package com.nowaiting.common.api; +package com.nowait.common.api; -import com.nowaiting.common.enums.HttpStatusCode; +import com.nowait.common.enums.HttpStatusCode; /** * API 에러 정보 diff --git a/common/src/main/java/com/nowaiting/common/api/ApiResult.java b/common/src/main/java/com/nowait/common/api/ApiResult.java similarity index 92% rename from common/src/main/java/com/nowaiting/common/api/ApiResult.java rename to common/src/main/java/com/nowait/common/api/ApiResult.java index 82e9a161..73509f56 100644 --- a/common/src/main/java/com/nowaiting/common/api/ApiResult.java +++ b/common/src/main/java/com/nowait/common/api/ApiResult.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.api; +package com.nowait.common.api; public class ApiResult { private final boolean success; diff --git a/common/src/main/java/com/nowaiting/common/api/ApiUtils.java b/common/src/main/java/com/nowait/common/api/ApiUtils.java similarity index 85% rename from common/src/main/java/com/nowaiting/common/api/ApiUtils.java rename to common/src/main/java/com/nowait/common/api/ApiUtils.java index 6cd010d3..1517f253 100644 --- a/common/src/main/java/com/nowaiting/common/api/ApiUtils.java +++ b/common/src/main/java/com/nowait/common/api/ApiUtils.java @@ -1,7 +1,7 @@ // common/api/ApiUtils.java -package com.nowaiting.common.api; +package com.nowait.common.api; -import com.nowaiting.common.enums.HttpStatusCode; +import com.nowait.common.enums.HttpStatusCode; public class ApiUtils { public static ApiResult success(T response) { diff --git a/common/src/main/java/com/nowaiting/common/enums/HttpStatusCode.java b/common/src/main/java/com/nowait/common/enums/HttpStatusCode.java similarity index 88% rename from common/src/main/java/com/nowaiting/common/enums/HttpStatusCode.java rename to common/src/main/java/com/nowait/common/enums/HttpStatusCode.java index a22fe922..5d96e2eb 100644 --- a/common/src/main/java/com/nowaiting/common/enums/HttpStatusCode.java +++ b/common/src/main/java/com/nowait/common/enums/HttpStatusCode.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.enums; +package com.nowait.common.enums; public enum HttpStatusCode { OK(200), diff --git a/common/src/main/java/com/nowaiting/common/enums/ReservationStatus.java b/common/src/main/java/com/nowait/common/enums/ReservationStatus.java similarity index 67% rename from common/src/main/java/com/nowaiting/common/enums/ReservationStatus.java rename to common/src/main/java/com/nowait/common/enums/ReservationStatus.java index 597c460c..4d4f58a2 100644 --- a/common/src/main/java/com/nowaiting/common/enums/ReservationStatus.java +++ b/common/src/main/java/com/nowait/common/enums/ReservationStatus.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.enums; +package com.nowait.common.enums; public enum ReservationStatus { WAITING,CALLING, CONFIRMED, CANCELLED diff --git a/common/src/main/java/com/nowaiting/common/enums/Role.java b/common/src/main/java/com/nowait/common/enums/Role.java similarity index 94% rename from common/src/main/java/com/nowaiting/common/enums/Role.java rename to common/src/main/java/com/nowait/common/enums/Role.java index c0a87c2e..5aed5a91 100644 --- a/common/src/main/java/com/nowaiting/common/enums/Role.java +++ b/common/src/main/java/com/nowait/common/enums/Role.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.enums; +package com.nowait.common.enums; public enum Role { USER("USER"), diff --git a/common/src/main/java/com/nowaiting/common/enums/SocialType.java b/common/src/main/java/com/nowait/common/enums/SocialType.java similarity index 62% rename from common/src/main/java/com/nowaiting/common/enums/SocialType.java rename to common/src/main/java/com/nowait/common/enums/SocialType.java index a853e826..1e46a7c6 100644 --- a/common/src/main/java/com/nowaiting/common/enums/SocialType.java +++ b/common/src/main/java/com/nowait/common/enums/SocialType.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.enums; +package com.nowait.common.enums; public enum SocialType { KAKAO, NAVER, GOOGLE, LOCAL diff --git a/common/src/main/java/com/nowaiting/common/exception/ErrorMessage.java b/common/src/main/java/com/nowait/common/exception/ErrorMessage.java similarity index 96% rename from common/src/main/java/com/nowaiting/common/exception/ErrorMessage.java rename to common/src/main/java/com/nowait/common/exception/ErrorMessage.java index dc7e34da..d695104f 100644 --- a/common/src/main/java/com/nowaiting/common/exception/ErrorMessage.java +++ b/common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.exception; +package com.nowait.common.exception; public enum ErrorMessage { // global diff --git a/common/src/main/java/com/nowaiting/common/exception/ErrorResponse.java b/common/src/main/java/com/nowait/common/exception/ErrorResponse.java similarity index 94% rename from common/src/main/java/com/nowaiting/common/exception/ErrorResponse.java rename to common/src/main/java/com/nowait/common/exception/ErrorResponse.java index 935069e7..1e0f70a7 100644 --- a/common/src/main/java/com/nowaiting/common/exception/ErrorResponse.java +++ b/common/src/main/java/com/nowait/common/exception/ErrorResponse.java @@ -1,4 +1,4 @@ -package com.nowaiting.common.exception; +package com.nowait.common.exception; import java.util.HashMap; import java.util.Map; diff --git a/domain-bookmark/src/main/java/com/nowait/domainbookmark/entity/Bookmark.java b/domain-bookmark/src/main/java/com/nowait/bookmark/entity/Bookmark.java similarity index 90% rename from domain-bookmark/src/main/java/com/nowait/domainbookmark/entity/Bookmark.java rename to domain-bookmark/src/main/java/com/nowait/bookmark/entity/Bookmark.java index 18f69e0c..d1808277 100644 --- a/domain-bookmark/src/main/java/com/nowait/domainbookmark/entity/Bookmark.java +++ b/domain-bookmark/src/main/java/com/nowait/bookmark/entity/Bookmark.java @@ -1,8 +1,8 @@ -package com.nowait.domainbookmark.entity; +package com.nowait.bookmark.entity; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; import com.nowait.base.BaseTimeEntity; -import com.nowaiting.user.entity.User; +import com.nowait.user.entity.User; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; diff --git a/domain-bookmark/src/main/java/com/nowait/domainbookmark/exception/BookmarkOwnerMismatchException.java b/domain-bookmark/src/main/java/com/nowait/bookmark/exception/BookmarkOwnerMismatchException.java similarity index 64% rename from domain-bookmark/src/main/java/com/nowait/domainbookmark/exception/BookmarkOwnerMismatchException.java rename to domain-bookmark/src/main/java/com/nowait/bookmark/exception/BookmarkOwnerMismatchException.java index 733d40d9..dcb2549a 100644 --- a/domain-bookmark/src/main/java/com/nowait/domainbookmark/exception/BookmarkOwnerMismatchException.java +++ b/domain-bookmark/src/main/java/com/nowait/bookmark/exception/BookmarkOwnerMismatchException.java @@ -1,6 +1,6 @@ -package com.nowait.domainbookmark.exception; +package com.nowait.bookmark.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class BookmarkOwnerMismatchException extends RuntimeException { public BookmarkOwnerMismatchException() { diff --git a/domain-bookmark/src/main/java/com/nowait/domainbookmark/exception/DuplicateBookmarkException.java b/domain-bookmark/src/main/java/com/nowait/bookmark/exception/DuplicateBookmarkException.java similarity index 63% rename from domain-bookmark/src/main/java/com/nowait/domainbookmark/exception/DuplicateBookmarkException.java rename to domain-bookmark/src/main/java/com/nowait/bookmark/exception/DuplicateBookmarkException.java index 29e70466..d18b4371 100644 --- a/domain-bookmark/src/main/java/com/nowait/domainbookmark/exception/DuplicateBookmarkException.java +++ b/domain-bookmark/src/main/java/com/nowait/bookmark/exception/DuplicateBookmarkException.java @@ -1,6 +1,6 @@ -package com.nowait.domainbookmark.exception; +package com.nowait.bookmark.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class DuplicateBookmarkException extends RuntimeException { public DuplicateBookmarkException() { diff --git a/domain-bookmark/src/main/java/com/nowait/domainbookmark/repository/BookmarkRepository.java b/domain-bookmark/src/main/java/com/nowait/bookmark/repository/BookmarkRepository.java similarity index 65% rename from domain-bookmark/src/main/java/com/nowait/domainbookmark/repository/BookmarkRepository.java rename to domain-bookmark/src/main/java/com/nowait/bookmark/repository/BookmarkRepository.java index 1865bfea..e5a2a10d 100644 --- a/domain-bookmark/src/main/java/com/nowait/domainbookmark/repository/BookmarkRepository.java +++ b/domain-bookmark/src/main/java/com/nowait/bookmark/repository/BookmarkRepository.java @@ -1,13 +1,13 @@ -package com.nowait.domainbookmark.repository; +package com.nowait.bookmark.repository; import java.util.Collection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.example.domainstore.entity.Store; -import com.nowait.domainbookmark.entity.Bookmark; -import com.nowaiting.user.entity.User; +import com.nowait.store.entity.Store; +import com.nowait.bookmark.entity.Bookmark; +import com.nowait.user.entity.User; @Repository public interface BookmarkRepository extends JpaRepository { diff --git a/domain-menu/src/main/java/com/example/menu/entity/Menu.java b/domain-menu/src/main/java/com/nowait/menu/entity/Menu.java similarity index 97% rename from domain-menu/src/main/java/com/example/menu/entity/Menu.java rename to domain-menu/src/main/java/com/nowait/menu/entity/Menu.java index 711bc779..31b280ae 100644 --- a/domain-menu/src/main/java/com/example/menu/entity/Menu.java +++ b/domain-menu/src/main/java/com/nowait/menu/entity/Menu.java @@ -1,4 +1,4 @@ -package com.example.menu.entity; +package com.nowait.menu.entity; import java.time.LocalDateTime; import com.nowait.base.BaseTimeEntity; diff --git a/domain-menu/src/main/java/com/example/menu/entity/MenuImage.java b/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java similarity index 96% rename from domain-menu/src/main/java/com/example/menu/entity/MenuImage.java rename to domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java index 6269ad43..c7dc602f 100644 --- a/domain-menu/src/main/java/com/example/menu/entity/MenuImage.java +++ b/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java @@ -1,4 +1,4 @@ -package com.example.menu.entity; +package com.nowait.menu.entity; import com.nowait.base.BaseTimeEntity; diff --git a/domain-menu/src/main/java/com/example/menu/repository/MenuImageRepository.java b/domain-menu/src/main/java/com/nowait/menu/repository/MenuImageRepository.java similarity index 70% rename from domain-menu/src/main/java/com/example/menu/repository/MenuImageRepository.java rename to domain-menu/src/main/java/com/nowait/menu/repository/MenuImageRepository.java index 05e125b8..11615149 100644 --- a/domain-menu/src/main/java/com/example/menu/repository/MenuImageRepository.java +++ b/domain-menu/src/main/java/com/nowait/menu/repository/MenuImageRepository.java @@ -1,12 +1,12 @@ -package com.example.menu.repository; +package com.nowait.menu.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.example.menu.entity.Menu; -import com.example.menu.entity.MenuImage; +import com.nowait.menu.entity.Menu; +import com.nowait.menu.entity.MenuImage; @Repository public interface MenuImageRepository extends JpaRepository { diff --git a/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java b/domain-menu/src/main/java/com/nowait/menu/repository/MenuRepository.java similarity index 85% rename from domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java rename to domain-menu/src/main/java/com/nowait/menu/repository/MenuRepository.java index ca15d517..fbe2282b 100644 --- a/domain-menu/src/main/java/com/example/menu/repository/MenuRepository.java +++ b/domain-menu/src/main/java/com/nowait/menu/repository/MenuRepository.java @@ -1,4 +1,4 @@ -package com.example.menu.repository; +package com.nowait.menu.repository; import java.util.List; import java.util.Optional; @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.example.menu.entity.Menu; +import com.nowait.menu.entity.Menu; @Repository public interface MenuRepository extends JpaRepository { diff --git a/domain-order/src/main/java/com/nowait/domainorder/entity/OrderItem.java b/domain-order/src/main/java/com/nowait/order/entity/OrderItem.java similarity index 92% rename from domain-order/src/main/java/com/nowait/domainorder/entity/OrderItem.java rename to domain-order/src/main/java/com/nowait/order/entity/OrderItem.java index 064fc166..a979933a 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/entity/OrderItem.java +++ b/domain-order/src/main/java/com/nowait/order/entity/OrderItem.java @@ -1,6 +1,6 @@ -package com.nowait.domainorder.entity; +package com.nowait.order.entity; -import com.example.menu.entity.Menu; +import com.nowait.menu.entity.Menu; import com.nowait.base.BaseTimeEntity; import jakarta.persistence.Entity; diff --git a/domain-order/src/main/java/com/nowait/domainorder/entity/UserOrder.java b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java similarity index 93% rename from domain-order/src/main/java/com/nowait/domainorder/entity/UserOrder.java rename to domain-order/src/main/java/com/nowait/order/entity/UserOrder.java index 8831f74a..7d94e44e 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/entity/UserOrder.java +++ b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java @@ -1,9 +1,9 @@ -package com.nowait.domainorder.entity; +package com.nowait.order.entity; import java.util.ArrayList; import java.util.List; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; import com.nowait.base.BaseTimeEntity; import jakarta.persistence.CascadeType; diff --git a/domain-order/src/main/java/com/nowait/domainorder/exception/DuplicateOrderException.java b/domain-order/src/main/java/com/nowait/order/exception/DuplicateOrderException.java similarity index 62% rename from domain-order/src/main/java/com/nowait/domainorder/exception/DuplicateOrderException.java rename to domain-order/src/main/java/com/nowait/order/exception/DuplicateOrderException.java index a3e816cd..aee663cd 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/exception/DuplicateOrderException.java +++ b/domain-order/src/main/java/com/nowait/order/exception/DuplicateOrderException.java @@ -1,6 +1,6 @@ -package com.nowait.domainorder.exception; +package com.nowait.order.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class DuplicateOrderException extends RuntimeException { public DuplicateOrderException() { diff --git a/domain-order/src/main/java/com/nowait/domainorder/exception/OrderItemsEmptyException.java b/domain-order/src/main/java/com/nowait/order/exception/OrderItemsEmptyException.java similarity index 63% rename from domain-order/src/main/java/com/nowait/domainorder/exception/OrderItemsEmptyException.java rename to domain-order/src/main/java/com/nowait/order/exception/OrderItemsEmptyException.java index 70614798..416e101f 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/exception/OrderItemsEmptyException.java +++ b/domain-order/src/main/java/com/nowait/order/exception/OrderItemsEmptyException.java @@ -1,6 +1,6 @@ -package com.nowait.domainorder.exception; +package com.nowait.order.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class OrderItemsEmptyException extends RuntimeException { public OrderItemsEmptyException() { diff --git a/domain-order/src/main/java/com/nowait/domainorder/exception/OrderParameterEmptyException.java b/domain-order/src/main/java/com/nowait/order/exception/OrderParameterEmptyException.java similarity index 65% rename from domain-order/src/main/java/com/nowait/domainorder/exception/OrderParameterEmptyException.java rename to domain-order/src/main/java/com/nowait/order/exception/OrderParameterEmptyException.java index d3b6736c..197814c4 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/exception/OrderParameterEmptyException.java +++ b/domain-order/src/main/java/com/nowait/order/exception/OrderParameterEmptyException.java @@ -1,6 +1,6 @@ -package com.nowait.domainorder.exception; +package com.nowait.order.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class OrderParameterEmptyException extends RuntimeException { public OrderParameterEmptyException() { diff --git a/domain-order/src/main/java/com/nowait/domainorder/repository/OrderItemRepository.java b/domain-order/src/main/java/com/nowait/order/repository/OrderItemRepository.java similarity index 69% rename from domain-order/src/main/java/com/nowait/domainorder/repository/OrderItemRepository.java rename to domain-order/src/main/java/com/nowait/order/repository/OrderItemRepository.java index 949b4e78..7544e3d0 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/repository/OrderItemRepository.java +++ b/domain-order/src/main/java/com/nowait/order/repository/OrderItemRepository.java @@ -1,9 +1,9 @@ -package com.nowait.domainorder.repository; +package com.nowait.order.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.domainorder.entity.OrderItem; +import com.nowait.order.entity.OrderItem; @Repository public interface OrderItemRepository extends JpaRepository { diff --git a/domain-order/src/main/java/com/nowait/domainorder/repository/OrderRepository.java b/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java similarity index 83% rename from domain-order/src/main/java/com/nowait/domainorder/repository/OrderRepository.java rename to domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java index 94e7a09e..e41b80de 100644 --- a/domain-order/src/main/java/com/nowait/domainorder/repository/OrderRepository.java +++ b/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java @@ -1,4 +1,4 @@ -package com.nowait.domainorder.repository; +package com.nowait.order.repository; import java.time.LocalDateTime; import java.util.List; @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.domainorder.entity.UserOrder; +import com.nowait.order.entity.UserOrder; @Repository public interface OrderRepository extends JpaRepository { diff --git a/domain-reservation/src/main/java/com/nowait/domainreservation/entity/Reservation.java b/domain-reservation/src/main/java/com/nowait/reservation/entity/Reservation.java similarity index 88% rename from domain-reservation/src/main/java/com/nowait/domainreservation/entity/Reservation.java rename to domain-reservation/src/main/java/com/nowait/reservation/entity/Reservation.java index 90b9eeb3..67463687 100644 --- a/domain-reservation/src/main/java/com/nowait/domainreservation/entity/Reservation.java +++ b/domain-reservation/src/main/java/com/nowait/reservation/entity/Reservation.java @@ -1,10 +1,10 @@ -package com.nowait.domainreservation.entity; +package com.nowait.reservation.entity; import java.time.LocalDateTime; -import com.example.domainstore.entity.Store; -import com.nowaiting.common.enums.ReservationStatus; -import com.nowaiting.user.entity.User; +import com.nowait.store.entity.Store; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.user.entity.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/domain-reservation/src/main/java/com/nowait/domainreservation/repository/ReservationRepository.java b/domain-reservation/src/main/java/com/nowait/reservation/repository/ReservationRepository.java similarity index 70% rename from domain-reservation/src/main/java/com/nowait/domainreservation/repository/ReservationRepository.java rename to domain-reservation/src/main/java/com/nowait/reservation/repository/ReservationRepository.java index 3483e91b..e564ff78 100644 --- a/domain-reservation/src/main/java/com/nowait/domainreservation/repository/ReservationRepository.java +++ b/domain-reservation/src/main/java/com/nowait/reservation/repository/ReservationRepository.java @@ -1,10 +1,10 @@ -package com.nowait.domainreservation.repository; +package com.nowait.reservation.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import com.nowait.domainreservation.entity.Reservation; +import com.nowait.reservation.entity.Reservation; public interface ReservationRepository extends JpaRepository { List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); diff --git a/domain-store/src/main/java/com/example/domainstore/entity/Store.java b/domain-store/src/main/java/com/nowait/store/entity/Store.java similarity index 97% rename from domain-store/src/main/java/com/example/domainstore/entity/Store.java rename to domain-store/src/main/java/com/nowait/store/entity/Store.java index 17464734..abd03960 100644 --- a/domain-store/src/main/java/com/example/domainstore/entity/Store.java +++ b/domain-store/src/main/java/com/nowait/store/entity/Store.java @@ -1,4 +1,4 @@ -package com.example.domainstore.entity; +package com.nowait.store.entity; import java.time.LocalDateTime; diff --git a/domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java b/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java similarity index 96% rename from domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java rename to domain-store/src/main/java/com/nowait/store/entity/StoreImage.java index 41f0896f..cddb9ad1 100644 --- a/domain-store/src/main/java/com/example/domainstore/entity/StoreImage.java +++ b/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java @@ -1,4 +1,4 @@ -package com.example.domainstore.entity; +package com.nowait.store.entity; import com.nowait.base.BaseTimeEntity; diff --git a/domain-store/src/main/java/com/example/domainstore/repository/StoreImageRepository.java b/domain-store/src/main/java/com/nowait/store/repository/StoreImageRepository.java similarity index 66% rename from domain-store/src/main/java/com/example/domainstore/repository/StoreImageRepository.java rename to domain-store/src/main/java/com/nowait/store/repository/StoreImageRepository.java index dcbe07ba..0617122e 100644 --- a/domain-store/src/main/java/com/example/domainstore/repository/StoreImageRepository.java +++ b/domain-store/src/main/java/com/nowait/store/repository/StoreImageRepository.java @@ -1,12 +1,12 @@ -package com.example.domainstore.repository; +package com.nowait.store.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.example.domainstore.entity.Store; -import com.example.domainstore.entity.StoreImage; +import com.nowait.store.entity.Store; +import com.nowait.store.entity.StoreImage; @Repository public interface StoreImageRepository extends JpaRepository { diff --git a/domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java b/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java similarity index 86% rename from domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java rename to domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java index 1d817652..0c3c7013 100644 --- a/domain-store/src/main/java/com/example/domainstore/repository/StoreRepository.java +++ b/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java @@ -1,4 +1,4 @@ -package com.example.domainstore.repository; +package com.nowait.store.repository; import java.util.List; import java.util.Optional; @@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.example.domainstore.entity.Store; +import com.nowait.store.entity.Store; @Repository public interface StoreRepository extends JpaRepository { diff --git a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java b/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java deleted file mode 100644 index 05dc5c67..00000000 --- a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.domaintoken.exception; - -import com.nowaiting.common.exception.ErrorMessage; - -public abstract class BusinessException extends RuntimeException { - private final ErrorMessage errorMessage; - - protected BusinessException(ErrorMessage errorMessage) { - super(errorMessage.getMessage()); - this.errorMessage = errorMessage; - } - - public String getCode() { - return errorMessage.getCode(); - } -} diff --git a/domain-token/src/main/java/com/example/domaintoken/entity/Token.java b/domain-token/src/main/java/com/nowait/token/entity/Token.java similarity index 94% rename from domain-token/src/main/java/com/example/domaintoken/entity/Token.java rename to domain-token/src/main/java/com/nowait/token/entity/Token.java index d337bdd5..16311bfa 100644 --- a/domain-token/src/main/java/com/example/domaintoken/entity/Token.java +++ b/domain-token/src/main/java/com/nowait/token/entity/Token.java @@ -1,8 +1,8 @@ -package com.example.domaintoken.entity; +package com.nowait.token.entity; import java.time.LocalDateTime; -import com.nowaiting.user.entity.User; +import com.nowait.user.entity.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/security-front/src/main/java/com/nowait/exception/BusinessException.java b/domain-token/src/main/java/com/nowait/token/exception/BusinessException.java similarity index 78% rename from security-front/src/main/java/com/nowait/exception/BusinessException.java rename to domain-token/src/main/java/com/nowait/token/exception/BusinessException.java index 60eec165..b9df2243 100644 --- a/security-front/src/main/java/com/nowait/exception/BusinessException.java +++ b/domain-token/src/main/java/com/nowait/token/exception/BusinessException.java @@ -1,4 +1,6 @@ -package com.nowait.exception; +package com.nowait.token.exception; + +import com.nowait.common.exception.ErrorMessage; public abstract class BusinessException extends RuntimeException { private final ErrorMessage errorMessage; diff --git a/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java b/domain-token/src/main/java/com/nowait/token/repository/TokenRepository.java similarity index 70% rename from domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java rename to domain-token/src/main/java/com/nowait/token/repository/TokenRepository.java index d813fd09..53d49d76 100644 --- a/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java +++ b/domain-token/src/main/java/com/nowait/token/repository/TokenRepository.java @@ -1,10 +1,10 @@ -package com.example.domaintoken.repository; +package com.nowait.token.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import com.example.domaintoken.entity.Token; +import com.nowait.token.entity.Token; public interface TokenRepository extends JpaRepository { Optional findByUserId(Long userId); diff --git a/domain-token/src/test/java/com/example/domaintoken/DomainTokenApplicationTests.java b/domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java similarity index 85% rename from domain-token/src/test/java/com/example/domaintoken/DomainTokenApplicationTests.java rename to domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java index 6a53ad8c..99f6cd30 100644 --- a/domain-token/src/test/java/com/example/domaintoken/DomainTokenApplicationTests.java +++ b/domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java @@ -1,4 +1,4 @@ -package com.example.domaintoken; +package com.nowait.token; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/domain-user/src/main/java/com/nowaiting/user/entity/MemberDetails.java b/domain-user/src/main/java/com/nowait/user/entity/MemberDetails.java similarity index 97% rename from domain-user/src/main/java/com/nowaiting/user/entity/MemberDetails.java rename to domain-user/src/main/java/com/nowait/user/entity/MemberDetails.java index c2c512f1..247b5ae8 100644 --- a/domain-user/src/main/java/com/nowaiting/user/entity/MemberDetails.java +++ b/domain-user/src/main/java/com/nowait/user/entity/MemberDetails.java @@ -1,4 +1,4 @@ -package com.nowaiting.user.entity; +package com.nowait.user.entity; import java.util.Collection; import java.util.List; diff --git a/domain-user/src/main/java/com/nowaiting/user/entity/User.java b/domain-user/src/main/java/com/nowait/user/entity/User.java similarity index 94% rename from domain-user/src/main/java/com/nowaiting/user/entity/User.java rename to domain-user/src/main/java/com/nowait/user/entity/User.java index 164d86a4..71e68ecb 100644 --- a/domain-user/src/main/java/com/nowaiting/user/entity/User.java +++ b/domain-user/src/main/java/com/nowait/user/entity/User.java @@ -1,9 +1,9 @@ -package com.nowaiting.user.entity; +package com.nowait.user.entity; import org.springframework.security.crypto.password.PasswordEncoder; -import com.nowaiting.common.enums.Role; -import com.nowaiting.common.enums.SocialType; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/domain-user/src/main/java/com/nowaiting/user/exception/UserNotFoundException.java b/domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java similarity index 62% rename from domain-user/src/main/java/com/nowaiting/user/exception/UserNotFoundException.java rename to domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java index bd2dfdc9..74788aa9 100644 --- a/domain-user/src/main/java/com/nowaiting/user/exception/UserNotFoundException.java +++ b/domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java @@ -1,6 +1,6 @@ -package com.nowaiting.user.exception; +package com.nowait.user.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class UserNotFoundException extends RuntimeException { public UserNotFoundException() { diff --git a/domain-user/src/main/java/com/nowaiting/user/repository/UserRepository.java b/domain-user/src/main/java/com/nowait/user/repository/UserRepository.java similarity index 76% rename from domain-user/src/main/java/com/nowaiting/user/repository/UserRepository.java rename to domain-user/src/main/java/com/nowait/user/repository/UserRepository.java index eea680f1..331264a3 100644 --- a/domain-user/src/main/java/com/nowaiting/user/repository/UserRepository.java +++ b/domain-user/src/main/java/com/nowait/user/repository/UserRepository.java @@ -1,10 +1,10 @@ -package com.nowaiting.user.repository; +package com.nowait.user.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import com.nowaiting.user.entity.User; +import com.nowait.user.entity.User; public interface UserRepository extends JpaRepository { Optional findByEmail(String email); diff --git a/external-oauth/.gitignore b/external-oauth/.gitignore new file mode 100644 index 00000000..c2065bc2 --- /dev/null +++ b/external-oauth/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/external-oauth/build.gradle b/external-oauth/build.gradle new file mode 100644 index 00000000..8d44e560 --- /dev/null +++ b/external-oauth/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'java-library' +} + +group = 'com.nowaiting' +version = rootProject.version + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':common') + implementation project(':domain-user') + implementation project(':domain-base') + implementation project(':domain-token') + + // SPRING SECURITY + api 'org.springframework.boot:spring-boot-starter-security' + api 'org.springframework.boot:spring-boot-starter-test' + // Lombok (optional) + compileOnly 'org.projectlombok:lombok:1.18.26' + annotationProcessor 'org.projectlombok:lombok:1.18.26' + // jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + // OAUTH2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' +} diff --git a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java b/external-oauth/src/main/java/com/nowait/externaloauth/dto/CustomOAuth2User.java similarity index 93% rename from security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java rename to external-oauth/src/main/java/com/nowait/externaloauth/dto/CustomOAuth2User.java index 91eaaea9..41a5b1b1 100644 --- a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/dto/CustomOAuth2User.java @@ -1,4 +1,4 @@ -package com.nowait.auth.dto; +package com.nowait.externaloauth.dto; import java.util.ArrayList; import java.util.Collection; @@ -7,7 +7,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.user.OAuth2User; -import com.nowaiting.user.entity.User; +import com.nowait.user.entity.User; import lombok.RequiredArgsConstructor; diff --git a/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java b/external-oauth/src/main/java/com/nowait/externaloauth/dto/KaKaoResponse.java similarity index 96% rename from security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java rename to external-oauth/src/main/java/com/nowait/externaloauth/dto/KaKaoResponse.java index e745bd26..8808617b 100644 --- a/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/dto/KaKaoResponse.java @@ -1,4 +1,4 @@ -package com.nowait.auth.dto; +package com.nowait.externaloauth.dto; import java.util.Map; diff --git a/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java b/external-oauth/src/main/java/com/nowait/externaloauth/dto/OAuth2Response.java similarity index 91% rename from security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java rename to external-oauth/src/main/java/com/nowait/externaloauth/dto/OAuth2Response.java index de987480..99cb1932 100644 --- a/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/dto/OAuth2Response.java @@ -1,4 +1,4 @@ -package com.nowait.auth.dto; +package com.nowait.externaloauth.dto; public interface OAuth2Response { // 제공자 (ex. naver, kakao) diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java b/external-oauth/src/main/java/com/nowait/externaloauth/jwt/JwtAuthorizationFilter.java similarity index 93% rename from security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java rename to external-oauth/src/main/java/com/nowait/externaloauth/jwt/JwtAuthorizationFilter.java index d55f9a5f..8494a235 100644 --- a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/jwt/JwtAuthorizationFilter.java @@ -1,4 +1,4 @@ -package com.nowait.auth.jwt; +package com.nowait.externaloauth.jwt; import java.io.IOException; @@ -6,10 +6,10 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowaiting.common.enums.Role; -import com.nowaiting.common.enums.SocialType; -import com.nowaiting.user.entity.User; +import com.nowait.externaloauth.dto.CustomOAuth2User; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.user.entity.User; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java b/external-oauth/src/main/java/com/nowait/externaloauth/jwt/JwtUtil.java similarity index 98% rename from security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java rename to external-oauth/src/main/java/com/nowait/externaloauth/jwt/JwtUtil.java index fd0620e9..72a17583 100644 --- a/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/jwt/JwtUtil.java @@ -1,4 +1,4 @@ -package com.nowait.auth.jwt; +package com.nowait.externaloauth.jwt; import java.nio.charset.StandardCharsets; import java.util.Date; diff --git a/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java b/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/CustomOAuth2UserService.java similarity index 85% rename from security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java rename to external-oauth/src/main/java/com/nowait/externaloauth/oauth2/CustomOAuth2UserService.java index fbb16cfb..475ff257 100644 --- a/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package com.nowait.auth.oauth2; +package com.nowait.externaloauth.oauth2; import java.util.Optional; @@ -8,13 +8,13 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.auth.dto.KaKaoResponse; -import com.nowait.auth.dto.OAuth2Response; -import com.nowaiting.common.enums.Role; -import com.nowaiting.common.enums.SocialType; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.externaloauth.dto.CustomOAuth2User; +import com.nowait.externaloauth.dto.KaKaoResponse; +import com.nowait.externaloauth.dto.OAuth2Response; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java b/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java similarity index 91% rename from security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java rename to external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java index b483f6d9..ea397a72 100644 --- a/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java @@ -1,4 +1,4 @@ -package com.nowait.auth.oauth2; +package com.nowait.externaloauth.oauth2; import java.io.IOException; import java.time.LocalDateTime; @@ -11,12 +11,13 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import com.example.domaintoken.entity.Token; -import com.example.domaintoken.repository.TokenRepository; +import com.nowait.token.entity.Token; +import com.nowait.token.repository.TokenRepository; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.auth.jwt.JwtUtil; -import com.nowaiting.user.entity.User; + +import com.nowait.externaloauth.dto.CustomOAuth2User; +import com.nowait.externaloauth.jwt.JwtUtil; +import com.nowait.user.entity.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java b/external-oauth/src/main/java/com/nowait/externaloauth/service/CustomUserDetailService.java similarity index 83% rename from security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java rename to external-oauth/src/main/java/com/nowait/externaloauth/service/CustomUserDetailService.java index 53550a56..87b639e3 100644 --- a/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/service/CustomUserDetailService.java @@ -1,13 +1,13 @@ -package com.nowait.auth.service; +package com.nowait.externaloauth.service; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; -import com.nowaiting.user.entity.MemberDetails; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.user.entity.MemberDetails; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; import lombok.RequiredArgsConstructor; diff --git a/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java b/infra-aws/src/main/java/com/nowait/infraaws/config/AwsS3Config.java similarity index 95% rename from infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java rename to infra-aws/src/main/java/com/nowait/infraaws/config/AwsS3Config.java index 4eda87c2..e0470e0e 100644 --- a/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java +++ b/infra-aws/src/main/java/com/nowait/infraaws/config/AwsS3Config.java @@ -1,4 +1,4 @@ -package com.example.infrastorage.config; +package com.nowait.infraaws.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; diff --git a/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java b/infra-aws/src/main/java/com/nowait/infraaws/s3/S3Service.java similarity index 98% rename from infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java rename to infra-aws/src/main/java/com/nowait/infraaws/s3/S3Service.java index 20d9abf0..a2c586fc 100644 --- a/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java +++ b/infra-aws/src/main/java/com/nowait/infraaws/s3/S3Service.java @@ -1,4 +1,4 @@ -package com.example.infrastorage.s3; +package com.nowait.infraaws.s3; import java.io.InputStream; import java.util.UUID; diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/auth/service/CustomUserDetailService.java b/security-admin/src/main/java/com/nowait/adminsecurity/auth/service/CustomUserDetailService.java index 130f7669..6f1bd303 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/auth/service/CustomUserDetailService.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/auth/service/CustomUserDetailService.java @@ -5,9 +5,9 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; -import com.nowaiting.user.entity.MemberDetails; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.repository.UserRepository; +import com.nowait.user.entity.MemberDetails; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; import lombok.RequiredArgsConstructor; diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/exception/RefreshTokenNotFoundException.java b/security-admin/src/main/java/com/nowait/adminsecurity/exception/RefreshTokenNotFoundException.java index e02f5114..eda367d5 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/exception/RefreshTokenNotFoundException.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/exception/RefreshTokenNotFoundException.java @@ -1,6 +1,6 @@ package com.nowait.adminsecurity.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class RefreshTokenNotFoundException extends ResourceNotFoundException { diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/exception/ResourceNotFoundException.java b/security-admin/src/main/java/com/nowait/adminsecurity/exception/ResourceNotFoundException.java index 90b43f35..9b26d0ca 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/exception/ResourceNotFoundException.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/exception/ResourceNotFoundException.java @@ -1,6 +1,6 @@ package com.nowait.adminsecurity.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public abstract class ResourceNotFoundException extends RuntimeException { private final ErrorMessage errorMessage; diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/exception/TokenBadRequestException.java b/security-admin/src/main/java/com/nowait/adminsecurity/exception/TokenBadRequestException.java index 146a36b1..483b2193 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/exception/TokenBadRequestException.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/exception/TokenBadRequestException.java @@ -1,7 +1,7 @@ package com.nowait.adminsecurity.exception; -import com.example.domaintoken.exception.BusinessException; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.token.exception.BusinessException; +import com.nowait.common.exception.ErrorMessage; public class TokenBadRequestException extends BusinessException { public TokenBadRequestException() { diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/exception/UnauthorizedException.java b/security-admin/src/main/java/com/nowait/adminsecurity/exception/UnauthorizedException.java index 558e1e2a..c65b3bc6 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/exception/UnauthorizedException.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/exception/UnauthorizedException.java @@ -1,6 +1,6 @@ package com.nowait.adminsecurity.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public class UnauthorizedException extends RuntimeException { private final ErrorMessage errorMessage; diff --git a/security-front/build.gradle b/security-front/build.gradle index f5d61d33..5bb9ee1b 100644 --- a/security-front/build.gradle +++ b/security-front/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation project(':domain-user') implementation project(':domain-base') implementation project(':domain-token') + implementation project(':external-oauth') // SPRING SECURITY api 'org.springframework.boot:spring-boot-starter-security' api 'org.springframework.boot:spring-boot-starter-test' diff --git a/security-front/src/main/java/com/nowait/exception/ErrorMessage.java b/security-front/src/main/java/com/nowait/exception/ErrorMessage.java deleted file mode 100644 index c365a92e..00000000 --- a/security-front/src/main/java/com/nowait/exception/ErrorMessage.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.nowait.exception; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ErrorMessage { - // global - INVALID_INPUT_VALUE("입력값이 올바르지 않습니다.", "global001"), - - // auth - UNAUTHORIZED("권한이 없습니다", "auth001"), - - // token - REFRESH_TOKEN_NOT_FOUND("기존 리프레시 토큰을 찾을 수 없습니다.", "token001"), - DOES_NOT_MATCH_REFRESH_TOKEN("기존 리프레시 토큰이 일치하지 않습니다.", "token002"); - - private final String message; - private final String code; - -} diff --git a/security-front/src/main/java/com/nowait/exception/ErrorResponse.java b/security-front/src/main/java/com/nowait/exception/ErrorResponse.java deleted file mode 100644 index a15935e8..00000000 --- a/security-front/src/main/java/com/nowait/exception/ErrorResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.nowait.exception; - -import java.util.HashMap; -import java.util.Map; - -import lombok.Getter; - -@Getter -public class ErrorResponse { - private final String message; - private final String code; - private final Map errors; - - public ErrorResponse(String message, String code) { - this.message = message; - this.code = code; - errors = new HashMap<>(); - } - - public ErrorResponse(String message, String code, Map errors) { - this.message = message; - this.code = code; - this.errors = errors; - } - -} diff --git a/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java b/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java deleted file mode 100644 index b9f7c1e2..00000000 --- a/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.nowait.exception; - -import static com.nowait.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.*; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; - -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.validation.FieldError; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingRequestValueException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.multipart.MultipartException; - -import io.swagger.v3.oas.annotations.Hidden; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Hidden -@RestControllerAdvice -public class GlobalExceptionHandler { - - // OAUTH 인증 실패 에러처리 메서드 - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(OAuth2AuthenticationException.class) - public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e) { - log.error("handleOAuth2AuthenticationException", e); - - return new ErrorResponse("OAuth 인증 실패 : " + e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(BusinessException.class) - public ErrorResponse handleBusinessException(BusinessException e) { - log.error("handleBusinessException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MethodArgumentNotValidException.class) - public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - log.error("handleMethodArgumentNotValidException", e); - Map errors = getErrors(e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(HttpMessageNotReadableException.class) - public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - log.error("handleHttpMessageNotReadableException", e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(IllegalArgumentException.class) - public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { - log.error("handleIllegalArgumentException", e); - return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MissingRequestValueException.class) - public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { - log.error("handleMissingRequestValueExceptionException", e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = UNAUTHORIZED) - @ExceptionHandler(UnauthorizedException.class) - public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { - log.error("handleUnauthorizedExceptionException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = NOT_FOUND) - @ExceptionHandler(ResourceNotFoundException.class) - public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { - log.error("handleResourceNotFoundExceptionException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MultipartException.class) - public ErrorResponse handleMultipartException(MultipartException e) { - log.error("handleMultipartException", e); - return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - - private static Map getErrors(MethodArgumentNotValidException e) { - return e.getBindingResult() - .getAllErrors() - .stream() - .filter(ObjectError.class::isInstance) - .collect(Collectors.toMap( - error -> error instanceof FieldError ? ((FieldError)error).getField() : error.getObjectName(), - ObjectError::getDefaultMessage, - (msg1, msg2) -> msg1 + ";" + msg2 - )); - } - -} diff --git a/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java b/security-front/src/main/java/com/nowait/frontsecurity/exception/RefreshTokenNotFoundException.java similarity index 65% rename from security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java rename to security-front/src/main/java/com/nowait/frontsecurity/exception/RefreshTokenNotFoundException.java index 5365ef52..409fe133 100644 --- a/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java +++ b/security-front/src/main/java/com/nowait/frontsecurity/exception/RefreshTokenNotFoundException.java @@ -1,4 +1,6 @@ -package com.nowait.exception; +package com.nowait.frontsecurity.exception; + +import com.nowait.common.exception.ErrorMessage; public class RefreshTokenNotFoundException extends ResourceNotFoundException { diff --git a/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java b/security-front/src/main/java/com/nowait/frontsecurity/exception/ResourceNotFoundException.java similarity index 77% rename from security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java rename to security-front/src/main/java/com/nowait/frontsecurity/exception/ResourceNotFoundException.java index ae4e4b7b..75c05b16 100644 --- a/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java +++ b/security-front/src/main/java/com/nowait/frontsecurity/exception/ResourceNotFoundException.java @@ -1,4 +1,6 @@ -package com.nowait.exception; +package com.nowait.frontsecurity.exception; + +import com.nowait.common.exception.ErrorMessage; public abstract class ResourceNotFoundException extends RuntimeException { private final ErrorMessage errorMessage; diff --git a/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java b/security-front/src/main/java/com/nowait/frontsecurity/exception/TokenBadRequestException.java similarity index 52% rename from security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java rename to security-front/src/main/java/com/nowait/frontsecurity/exception/TokenBadRequestException.java index 504852ee..95a00a18 100644 --- a/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java +++ b/security-front/src/main/java/com/nowait/frontsecurity/exception/TokenBadRequestException.java @@ -1,4 +1,7 @@ -package com.nowait.exception; +package com.nowait.frontsecurity.exception; + +import com.nowait.common.exception.ErrorMessage; +import com.nowait.token.exception.BusinessException; public class TokenBadRequestException extends BusinessException { public TokenBadRequestException() { diff --git a/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java b/security-front/src/main/java/com/nowait/frontsecurity/exception/UnauthorizedException.java similarity index 82% rename from security-front/src/main/java/com/nowait/exception/UnauthorizedException.java rename to security-front/src/main/java/com/nowait/frontsecurity/exception/UnauthorizedException.java index 65a8fd0a..8ccedea3 100644 --- a/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java +++ b/security-front/src/main/java/com/nowait/frontsecurity/exception/UnauthorizedException.java @@ -1,4 +1,6 @@ -package com.nowait.exception; +package com.nowait.frontsecurity.exception; + +import com.nowait.common.exception.ErrorMessage; public class UnauthorizedException extends RuntimeException { private final ErrorMessage errorMessage; diff --git a/settings.gradle b/settings.gradle index 501d4ca7..4984deb9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -rootProject.name = 'nowaiting' +rootProject.name = 'nowait' include 'common' include 'domain-base' @@ -9,10 +9,11 @@ include 'domain-token' include 'domain-bookmark' include 'domain-order' include 'domain-reservation' -include 'api-user' -include 'api-admin' -include 'infra-aws' include 'security-admin' include 'security-front' +include 'external-oauth' +include 'infra-aws' include 'application-config' +include 'application-user' +include 'application-admin' From 3912c4546f325dc7d8320c9ae2ca09af3849ef88 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 15:39:07 +0900 Subject: [PATCH 039/685] =?UTF-8?q?refactor:=20cors=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/config/SecurityConfig.java | 3 +++ .../auth/config/SecurityConfig.java | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) rename security-front/src/main/java/com/nowait/{ => frontsecurity}/auth/config/SecurityConfig.java (85%) diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java b/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java index f45e5ec1..81214e1c 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java @@ -13,6 +13,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfigurationSource; import com.nowait.adminsecurity.auth.jwt.JwtAuthorizationFilter; import com.nowait.adminsecurity.auth.jwt.JwtUtil; @@ -26,10 +27,12 @@ public class SecurityConfig { private final JwtUtil jwtUtil; private final CustomUserDetailService customUserDetailService; + private final CorsConfigurationSource corsConfigurationSource; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource)) // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) .csrf(AbstractHttpConfigurer::disable) // 시큐리티 폼 로그인 비활성화 diff --git a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java b/security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java similarity index 85% rename from security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java rename to security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java index aa535f8c..8afe5fc2 100644 --- a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java +++ b/security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.nowait.auth.config; +package com.nowait.frontsecurity.auth.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,11 +14,11 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; -import com.nowait.auth.jwt.JwtAuthorizationFilter; -import com.nowait.auth.jwt.JwtUtil; -import com.nowait.auth.oauth2.CustomOAuth2UserService; -import com.nowait.auth.oauth2.OAuth2LoginSuccessHandler; -import com.nowait.auth.service.CustomUserDetailService; +import com.nowait.externaloauth.jwt.JwtAuthorizationFilter; +import com.nowait.externaloauth.jwt.JwtUtil; +import com.nowait.externaloauth.oauth2.CustomOAuth2UserService; +import com.nowait.externaloauth.oauth2.OAuth2LoginSuccessHandler; +import com.nowait.externaloauth.service.CustomUserDetailService; import lombok.RequiredArgsConstructor; @@ -27,13 +27,15 @@ @RequiredArgsConstructor public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; - private final OAuth2LoginSuccessHandler OAuth2LoginSuccessHandler; + private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; private final JwtUtil jwtUtil; private final CustomUserDetailService userDetailsService; + private final CorsConfigurationSource corsConfigurationSource; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource)) // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) .csrf(AbstractHttpConfigurer::disable) // 시큐리티 폼 로그인 비활성화 @@ -46,7 +48,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userService(customOAuth2UserService) - ).successHandler(OAuth2LoginSuccessHandler) + ).successHandler(oAuth2LoginSuccessHandler) ) // 세션 사용하지 않음 .sessionManagement(session -> From 3183a5d6be7a7aa0b077a6d9ae62d3c39111c9a8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:19:56 +0900 Subject: [PATCH 040/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workflows/{dev.yml => deploy-admin.yml} | 32 +++++++++++++++---- appspec-admin.yml | 20 ++++++++++++ build.gradle | 21 ++++++++---- common/build.gradle | 9 ++++-- .../token/DomainTokenApplicationTests.java | 13 -------- scripts/start-admin.sh | 25 +++++++++++++++ scripts/stop-admin.sh | 22 +++++++++++++ 7 files changed, 114 insertions(+), 28 deletions(-) rename .github/workflows/{dev.yml => deploy-admin.yml} (56%) create mode 100644 appspec-admin.yml delete mode 100644 domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java create mode 100644 scripts/start-admin.sh create mode 100644 scripts/stop-admin.sh diff --git a/.github/workflows/dev.yml b/.github/workflows/deploy-admin.yml similarity index 56% rename from .github/workflows/dev.yml rename to .github/workflows/deploy-admin.yml index 57f36953..ec98a5c9 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/deploy-admin.yml @@ -1,15 +1,26 @@ -name: Dev CICD +name: Dev Admin CI/CD on: push: branches: - develop + paths: + - 'application-admin/**' + - 'common/**' + - 'domain-*/**' + - 'application-config/**' + - 'security-admin/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' + env: PROJECT_NAME: NoWait + DIVISION: admin AWS_REGION: ap-northeast-2 AWS_S3_BUCKET: nowait-deploy-github-actions AWS_CODE_DEPLOY_APPLICATION: nowaiting - AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev + AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-admin jobs: build-with-gradle: @@ -30,11 +41,19 @@ jobs: - name: Set YML run: | mkdir -p src/main/resources - echo "${{ secrets.APPLICATION_DEV_YML }}" | base64 --decode > src/main/resources/application-dev.yml + echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > src/main/resources/application-admin.yml find src - name: 프로젝트 빌드 - run: ./gradlew clean build -x test + run: ./gradlew clean :application-admin:bootJar -x test + + - name: Prepare deployment package + run: | + rm -rf deploy && mkdir deploy + cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar + cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/appspec.yml + cd deploy + zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . - name: AWS credential 설정 uses: aws-actions/configure-aws-credentials@v1 @@ -44,7 +63,7 @@ jobs: aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - name: S3에 업로드 - run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/$PROJECT_NAME/$GITHUB_SHA.zip --source . + run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/$PROJECT_NAME/$DIVISION/$GITHUB_SHA.zip --source . - name: EC2에 배포 run: | @@ -52,6 +71,7 @@ jobs: --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ --deployment-config-name CodeDeployDefault.AllAtOnce \ --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ - --s3-location bucket=$AWS_S3_BUCKET,key=$PROJECT_NAME/$GITHUB_SHA.zip,bundleType=zip + --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip,bundleType=zip + diff --git a/appspec-admin.yml b/appspec-admin.yml new file mode 100644 index 00000000..49636f2b --- /dev/null +++ b/appspec-admin.yml @@ -0,0 +1,20 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/spring-github-action + overwrite: yes + +permissions: + - object: / + owner: ubuntu + group: ubuntu + +hooks: + AfterInstall: + - location: scripts/stop-admin.sh + timeout: 60 + ApplicationStart: + - location: scripts/start-admin.sh + timeout: 60 diff --git a/build.gradle b/build.gradle index 51bc6932..791934ae 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,20 @@ subprojects { group = rootProject.group version = rootProject.version + // 실행 모듈만 따로 정의 + def applicationModules = ['application-admin', 'application-user'] + + if (applicationModules.contains(project.name)) { + // 실제 Service 실행 모듈 + apply plugin: 'org.springframework.boot' + bootJar { enabled = true } + jar { enabled = false } + } else { + // 라이브러리/설정/도메인 전용 모듈 + bootJar { enabled = false } + jar { enabled = true } + } + repositories { mavenCentral() } @@ -43,11 +57,4 @@ subprojects { test { useJUnitPlatform() } - - bootJar { - enabled = false - } - jar { - enabled = true - } } diff --git a/common/build.gradle b/common/build.gradle index 75d320e1..44e9a75f 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,5 +1,10 @@ -bootJar { enabled = false } -jar { enabled = true } +jar { + enabled = true +} +bootJar { + enabled = false +} + dependencies { } diff --git a/domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java b/domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java deleted file mode 100644 index 99f6cd30..00000000 --- a/domain-token/src/test/java/com/nowait/token/DomainTokenApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.nowait.token; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class DomainTokenApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/scripts/start-admin.sh b/scripts/start-admin.sh new file mode 100644 index 00000000..3c543ad9 --- /dev/null +++ b/scripts/start-admin.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +REPOSITORY="/home/ubuntu/spring-github-action" +cd $REPOSITORY + +APP_NAME=application-admin +JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME + +START_LOG="$REPOSITORY/start.log" +ERROR_LOG="$REPOSITORY/error.log" +APP_LOG="$REPOSITORY/application.log" + +$ chmod 666 $START_LOG +$ chmod 666 ERROR_LOG +$ chmod 666 APP_LOG + +SERVICE_PID=$(pgrep -f ${APP_NAME}.*.jar) # 실행중인 Spring 서버의 PID +NOW=$(date +%c) + + +echo "[$NOW] > $JAR_PATH 실행" >> $START_LOG +nohup java -Xms256m -Xmx512m -XX:+UseG1GC -jar $JAR_PATH --spring.profiles.active=admin > $APP_LOG 2> $ERROR_LOG & + +echo "[$NOW] > 서비스 PID: $SERVICE_PID" >> $START_LOG diff --git a/scripts/stop-admin.sh b/scripts/stop-admin.sh new file mode 100644 index 00000000..868f77b9 --- /dev/null +++ b/scripts/stop-admin.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +REPOSITORY=/home/ubuntu/spring-github-action +cd $REPOSITORY + +APP_NAME=application-admin +JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME +STOP_LOG="$REPOSITORY/stop.log" + +$ chmod 666 STOP_LOG + +CURRENT_PID=$(pgrep -f ${APP_NAME}.*.jar) + +if [ -z $CURRENT_PID ] +then + echo "서비스 NouFound" >> $STOP_LOG +else + echo "> kill -15 $CURRENT_PID" + kill -15 $CURRENT_PID + sleep 5 +fi From c787006f233144a515c6be0431d491471abd3a18 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:30:59 +0900 Subject: [PATCH 041/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 11 ++++++++--- scripts/start-admin.sh | 6 +++--- scripts/start-user.sh | 25 +++++++++++++++++++++++++ scripts/stop-admin.sh | 2 +- scripts/stop-user.sh | 22 ++++++++++++++++++++++ 5 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 scripts/start-user.sh create mode 100644 scripts/stop-user.sh diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index ec98a5c9..195b6519 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -40,8 +40,8 @@ jobs: - name: Set YML run: | - mkdir -p src/main/resources - echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > src/main/resources/application-admin.yml + mkdir -p application-admin/src/main/resources + echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > application-admin/src/main/resources/application-admin.yml find src - name: 프로젝트 빌드 @@ -54,6 +54,7 @@ jobs: cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/appspec.yml cd deploy zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . + cd .. - name: AWS credential 설정 uses: aws-actions/configure-aws-credentials@v1 @@ -63,7 +64,11 @@ jobs: aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - name: S3에 업로드 - run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/$PROJECT_NAME/$DIVISION/$GITHUB_SHA.zip --source . + run: aws deploy push \ + --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ + --ignore-hidden-files \ + --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ + --source "${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip" - name: EC2에 배포 run: | diff --git a/scripts/start-admin.sh b/scripts/start-admin.sh index 3c543ad9..32b3ed6b 100644 --- a/scripts/start-admin.sh +++ b/scripts/start-admin.sh @@ -11,9 +11,9 @@ START_LOG="$REPOSITORY/start.log" ERROR_LOG="$REPOSITORY/error.log" APP_LOG="$REPOSITORY/application.log" -$ chmod 666 $START_LOG -$ chmod 666 ERROR_LOG -$ chmod 666 APP_LOG +$ chmod 666 "$START_LOG" +$ chmod 666 "$ERROR_LOG" +$ chmod 666 "$APP_LOG" SERVICE_PID=$(pgrep -f ${APP_NAME}.*.jar) # 실행중인 Spring 서버의 PID NOW=$(date +%c) diff --git a/scripts/start-user.sh b/scripts/start-user.sh new file mode 100644 index 00000000..eb4db17b --- /dev/null +++ b/scripts/start-user.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +REPOSITORY="/home/ubuntu/spring-github-action" +cd $REPOSITORY + +APP_NAME=application-admin +JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME + +START_LOG="$REPOSITORY/start.log" +ERROR_LOG="$REPOSITORY/error.log" +APP_LOG="$REPOSITORY/application.log" + +$ chmod 666 $START_LOG +$ chmod 666 ERROR_LOG +$ chmod 666 APP_LOG + +SERVICE_PID=$(pgrep -f ${APP_NAME}.*.jar) # 실행중인 Spring 서버의 PID +NOW=$(date +%c) + + +echo "[$NOW] > $JAR_PATH 실행" >> $START_LOG +nohup java -Xms256m -Xmx512m -XX:+UseG1GC -jar $JAR_PATH --spring.profiles.active=user > $APP_LOG 2> $ERROR_LOG & + +echo "[$NOW] > 서비스 PID: $SERVICE_PID" >> $START_LOG diff --git a/scripts/stop-admin.sh b/scripts/stop-admin.sh index 868f77b9..6d6ad0e4 100644 --- a/scripts/stop-admin.sh +++ b/scripts/stop-admin.sh @@ -8,7 +8,7 @@ JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME STOP_LOG="$REPOSITORY/stop.log" -$ chmod 666 STOP_LOG +$ chmod 666 "$STOP_LOG" CURRENT_PID=$(pgrep -f ${APP_NAME}.*.jar) diff --git a/scripts/stop-user.sh b/scripts/stop-user.sh new file mode 100644 index 00000000..868f77b9 --- /dev/null +++ b/scripts/stop-user.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +REPOSITORY=/home/ubuntu/spring-github-action +cd $REPOSITORY + +APP_NAME=application-admin +JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME +STOP_LOG="$REPOSITORY/stop.log" + +$ chmod 666 STOP_LOG + +CURRENT_PID=$(pgrep -f ${APP_NAME}.*.jar) + +if [ -z $CURRENT_PID ] +then + echo "서비스 NouFound" >> $STOP_LOG +else + echo "> kill -15 $CURRENT_PID" + kill -15 $CURRENT_PID + sleep 5 +fi From 8ac14cb5c13cf21aa21d75f6b58d92b11a81f684 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:38:39 +0900 Subject: [PATCH 042/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 195b6519..4e5d710b 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -42,7 +42,7 @@ jobs: run: | mkdir -p application-admin/src/main/resources echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > application-admin/src/main/resources/application-admin.yml - find src + find application-admin/src - name: 프로젝트 빌드 run: ./gradlew clean :application-admin:bootJar -x test From 9ea0a2f96d0518179031358fd3d196e9c44f7a1e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:47:09 +0900 Subject: [PATCH 043/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 4e5d710b..f0b82b17 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -42,7 +42,7 @@ jobs: run: | mkdir -p application-admin/src/main/resources echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > application-admin/src/main/resources/application-admin.yml - find application-admin/src +# find application-admin/src - name: 프로젝트 빌드 run: ./gradlew clean :application-admin:bootJar -x test From 8cbdfb8368dd5bbf8c34cbe8ff05493c178d60b8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:48:51 +0900 Subject: [PATCH 044/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 5 +---- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index f0b82b17..5d1ea730 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -42,7 +42,7 @@ jobs: run: | mkdir -p application-admin/src/main/resources echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > application-admin/src/main/resources/application-admin.yml -# find application-admin/src + find application-admin/src - name: 프로젝트 빌드 run: ./gradlew clean :application-admin:bootJar -x test @@ -77,6 +77,3 @@ jobs: --deployment-config-name CodeDeployDefault.AllAtOnce \ --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip,bundleType=zip - - - diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 5f43869a..a3347acb 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,3 +33,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From caf37bf1e8df38b52678cf57d4873387c2c9a4ec Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:51:34 +0900 Subject: [PATCH 045/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 2 +- .../java/com/nowait/applicationadmin/ApiAdminApplication.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 5d1ea730..252b55bf 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -51,7 +51,7 @@ jobs: run: | rm -rf deploy && mkdir deploy cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar - cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/appspec.yml + cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml cd deploy zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . cd .. diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index a3347acb..5f43869a 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,4 +33,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 5e9d34153e36ab5c77f060802a6ae130f5249fd6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:54:53 +0900 Subject: [PATCH 046/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 2 +- .../java/com/nowait/applicationadmin/ApiAdminApplication.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 252b55bf..0a445a6f 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -51,7 +51,7 @@ jobs: run: | rm -rf deploy && mkdir deploy cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar - cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml + cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/ cd deploy zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . cd .. diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 5f43869a..a3347acb 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,3 +33,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 97504a7b4ba056057567446de5de650d87fd316e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 18:58:36 +0900 Subject: [PATCH 047/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 11 ++++++----- .../nowait/applicationadmin/ApiAdminApplication.java | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 0a445a6f..402534c7 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -64,11 +64,12 @@ jobs: aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - name: S3에 업로드 - run: aws deploy push \ - --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ - --ignore-hidden-files \ - --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ - --source "${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip" + run: | + aws deploy push \ + --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ + --ignore-hidden-files \ + --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ + --source "${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip" - name: EC2에 배포 run: | diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index a3347acb..5f43869a 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,4 +33,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From cd55ed9df1e987e1878224de2270c10a9e237d56 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 20:17:13 +0900 Subject: [PATCH 048/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 8 ++++---- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 402534c7..ded6b60d 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -66,10 +66,10 @@ jobs: - name: S3에 업로드 run: | aws deploy push \ - --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ - --ignore-hidden-files \ - --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ - --source "${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip" + --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ + --ignore-hidden-files \ + --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ + --source deploy - name: EC2에 배포 run: | diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 5f43869a..a3347acb 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,3 +33,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From d6bae86ac5eebc9c57d6a2415ce1a7553b8a60d6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 20:19:59 +0900 Subject: [PATCH 049/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 106 +++++++++++++++-------------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index ded6b60d..36dbc96c 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -2,8 +2,8 @@ name: Dev Admin CI/CD on: push: - branches: - - develop + branches: + - develop paths: - 'application-admin/**' - 'common/**' @@ -21,60 +21,62 @@ env: AWS_S3_BUCKET: nowait-deploy-github-actions AWS_CODE_DEPLOY_APPLICATION: nowaiting AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-admin - + jobs: build-with-gradle: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: JDK 17 설치 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'zulu' - - - name: gradlew에 실행 권한 부여 - run: chmod +x ./gradlew + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Set YML + run: | + mkdir -p application-admin/src/main/resources + echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" \ + | base64 --decode \ + > application-admin/src/main/resources/application-admin.yml + find application-admin/src + + - name: Build Admin JAR + run: ./gradlew clean :application-admin:bootJar -x test + + - name: Prepare deployment package + run: | + rm -rf deploy && mkdir deploy + cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar + cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/ + cd deploy + zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . + cd .. - - name: Set YML - run: | - mkdir -p application-admin/src/main/resources - echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode > application-admin/src/main/resources/application-admin.yml - find application-admin/src - - - name: 프로젝트 빌드 - run: ./gradlew clean :application-admin:bootJar -x test + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - - name: Prepare deployment package - run: | - rm -rf deploy && mkdir deploy - cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar - cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/ - cd deploy - zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . - cd .. - - - name: AWS credential 설정 - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-region: ${{ env.AWS_REGION }} - aws-access-key-id: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - - - name: S3에 업로드 - run: | - aws deploy push \ - --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ - --ignore-hidden-files \ - --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ - --source deploy + - name: Upload to S3 + run: | + aws deploy push \ + --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ + --ignore-hidden-files \ + --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ + --source deploy - - name: EC2에 배포 - run: | - aws deploy create-deployment \ - --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ - --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip,bundleType=zip + - name: Create CodeDeploy deployment + run: | + aws deploy create-deployment \ + --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ + --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip,bundleType=zip From 78b1b9c1984bb0068bec6fe99cf97866ec1b964a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 20:22:54 +0900 Subject: [PATCH 050/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 36dbc96c..ae6ce8a7 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -53,7 +53,7 @@ jobs: run: | rm -rf deploy && mkdir deploy cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar - cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/ + cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/appspec.yml cd deploy zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . cd .. From 6b0d95037acd31aa7f5214f1dd34a1751eaba8fb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 20:23:50 +0900 Subject: [PATCH 051/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/applicationadmin/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index a3347acb..5f43869a 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,4 +33,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 74c6cbd225e91ca10dc54422a67bc00b2a5640a5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 20:28:16 +0900 Subject: [PATCH 052/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 7 ++++--- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index ae6ce8a7..bf923032 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -52,10 +52,11 @@ jobs: - name: Prepare deployment package run: | rm -rf deploy && mkdir deploy - cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar - cp scripts/start-admin.sh scripts/stop-admin.sh appspec-admin.yml deploy/appspec.yml + cp scripts/start-admin.sh scripts/stop-admin.sh deploy/ + cp appspec-admin.yml deploy/appspec.yml + cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar cd deploy - zip -r ../${{ env.PROJECT_NAME }}-${{ env.DIVISION }}-${{ github.sha }}.zip . + zip -r ../NoWait-admin-${GITHUB_SHA}.zip . cd .. - name: Configure AWS credentials diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 5f43869a..a3347acb 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -33,3 +33,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 823a268a508d9f79d8849d66feca1027a2b15143 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 21:53:47 +0900 Subject: [PATCH 053/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/applicationadmin/ApiAdminApplication.java | 2 -- appspec-admin.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index a3347acb..14ac1764 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -18,7 +18,6 @@ "com.nowait.user.entity", "com.nowait.reservation.entity", "com.nowait.order.entity" - }) @EnableJpaRepositories(basePackages = { "com.nowait.menu.repository", @@ -33,4 +32,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/appspec-admin.yml b/appspec-admin.yml index 49636f2b..66c44ad6 100644 --- a/appspec-admin.yml +++ b/appspec-admin.yml @@ -3,7 +3,7 @@ os: linux files: - source: / - destination: /home/ubuntu/spring-github-action + destination: /home/ubuntu/spring-github-action-admin overwrite: yes permissions: From 2862c5151c9293611f186a33b0d88cda827581b8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 21:59:57 +0900 Subject: [PATCH 054/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 5 +++-- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index bf923032..809501b7 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -51,8 +51,9 @@ jobs: - name: Prepare deployment package run: | - rm -rf deploy && mkdir deploy - cp scripts/start-admin.sh scripts/stop-admin.sh deploy/ + rm -rf deploy + mkdir -p deploy/scripts + cp scripts/start-admin.sh scripts/stop-admin.sh deploy/scripts/ cp appspec-admin.yml deploy/appspec.yml cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar cd deploy diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 14ac1764..95f08d3a 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -32,3 +32,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From bc50457469da6e4bae4d2e5644fb8ece27d3d57e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 22:14:05 +0900 Subject: [PATCH 055/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/start-admin.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/start-admin.sh b/scripts/start-admin.sh index 32b3ed6b..efb8a895 100644 --- a/scripts/start-admin.sh +++ b/scripts/start-admin.sh @@ -1,6 +1,6 @@ #!/bin/bash -REPOSITORY="/home/ubuntu/spring-github-action" +REPOSITORY="/home/ubuntu/spring-github-action-admin" cd $REPOSITORY APP_NAME=application-admin From 6f04454774df6f2bb0ab8cb4b6d708330829789f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 22:15:24 +0900 Subject: [PATCH 056/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/applicationadmin/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 95f08d3a..14ac1764 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -32,4 +32,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 68785d29b3e00be78f8f9ef625647a395c1afbe5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 30 Jun 2025 22:32:03 +0900 Subject: [PATCH 057/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 + scripts/start-admin.sh | 4 ++-- scripts/stop-admin.sh | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 14ac1764..95f08d3a 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -32,3 +32,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/scripts/start-admin.sh b/scripts/start-admin.sh index efb8a895..3fbec621 100644 --- a/scripts/start-admin.sh +++ b/scripts/start-admin.sh @@ -4,8 +4,8 @@ REPOSITORY="/home/ubuntu/spring-github-action-admin" cd $REPOSITORY APP_NAME=application-admin -JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) -JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME +JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/$JAR_NAME START_LOG="$REPOSITORY/start.log" ERROR_LOG="$REPOSITORY/error.log" diff --git a/scripts/stop-admin.sh b/scripts/stop-admin.sh index 6d6ad0e4..7fcbe738 100644 --- a/scripts/stop-admin.sh +++ b/scripts/stop-admin.sh @@ -1,11 +1,11 @@ #!/bin/bash -REPOSITORY=/home/ubuntu/spring-github-action +REPOSITORY="/home/ubuntu/spring-github-action-admin" cd $REPOSITORY APP_NAME=application-admin -JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) -JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME +JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/$JAR_NAME STOP_LOG="$REPOSITORY/stop.log" $ chmod 666 "$STOP_LOG" From b8126b3824603ad4d26eb0407dc575cd648c227a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 00:34:30 +0900 Subject: [PATCH 058/685] =?UTF-8?q?chore:=20CI/CD=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 7 +++---- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 809501b7..8e96047b 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -6,10 +6,11 @@ on: - develop paths: - 'application-admin/**' + - 'application-config/**' - 'common/**' - 'domain-*/**' - - 'application-config/**' - 'security-admin/**' + - 'infra-aws/**' - 'build.gradle' - 'settings.gradle' - 'gradle/**' @@ -41,9 +42,7 @@ jobs: - name: Set YML run: | mkdir -p application-admin/src/main/resources - echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" \ - | base64 --decode \ - > application-admin/src/main/resources/application-admin.yml + echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode application-admin/src/main/resources/application-admin.yml find application-admin/src - name: Build Admin JAR diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 95f08d3a..14ac1764 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -32,4 +32,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From d7c2c4c3b061e76130b10799b7581986d573bc23 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 00:34:44 +0900 Subject: [PATCH 059/685] =?UTF-8?q?chore:=20user=20CI/CD=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user.yml | 85 +++++++++++++++++++++++++++++++ appspec-user.yml | 20 ++++++++ scripts/start-user.sh | 12 ++--- scripts/stop-user.sh | 10 ++-- 4 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/deploy-user.yml create mode 100644 appspec-user.yml diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml new file mode 100644 index 00000000..449f6483 --- /dev/null +++ b/.github/workflows/deploy-user.yml @@ -0,0 +1,85 @@ +name: Dev User CI/CD + +on: + push: + branches: + - develop + paths: + - 'application-user/**' + - 'application-config/**' + - 'common/**' + - 'domain-*/**' + - 'security-front/**' + - 'external-oauth/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' + +env: + PROJECT_NAME: NoWait + DIVISION: user + AWS_REGION: ap-northeast-2 + AWS_S3_BUCKET: nowait-deploy-github-actions + AWS_CODE_DEPLOY_APPLICATION: nowaiting + AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-user + +jobs: + build-with-gradle: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: JDK 17 설치 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + + - name: gradlew에 실행 권한 부여 + run: chmod +x ./gradlew + + - name: Set YML + run: | + mkdir -p application-user/src/main/resources + echo "${{ secrets.APPLICATION_DEV_USER_YML }}" | base64 --decode application-user/src/main/resources/application-user.yml + find application-user/src + + - name: Build User JAR + run: ./gradlew clean :application-user:bootJar -x test + + - name: Prepare deployment package + run: | + rm -rf deploy + mkdir -p deploy/scripts + cp scripts/start-admin.sh scripts/stop-admin.sh deploy/scripts/ + cp appspec-admin.yml deploy/appspec.yml + cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar + cd deploy + zip -r ../NoWait-admin-${GITHUB_SHA}.zip . + cd .. + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} + + - name: Upload to S3 + run: | + aws deploy push \ + --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ + --ignore-hidden-files \ + --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ + --source deploy + + - name: Create CodeDeploy deployment + run: | + aws deploy create-deployment \ + --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ + --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip,bundleType=zip + + diff --git a/appspec-user.yml b/appspec-user.yml new file mode 100644 index 00000000..1edca8d4 --- /dev/null +++ b/appspec-user.yml @@ -0,0 +1,20 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/spring-github-action-user + overwrite: yes + +permissions: + - object: / + owner: ubuntu + group: ubuntu + +hooks: + AfterInstall: + - location: scripts/stop-user.sh + timeout: 60 + ApplicationStart: + - location: scripts/start-user.sh + timeout: 60 diff --git a/scripts/start-user.sh b/scripts/start-user.sh index eb4db17b..ad7dcafd 100644 --- a/scripts/start-user.sh +++ b/scripts/start-user.sh @@ -1,19 +1,19 @@ #!/bin/bash -REPOSITORY="/home/ubuntu/spring-github-action" +REPOSITORY="/home/ubuntu/spring-github-action-user" cd $REPOSITORY -APP_NAME=application-admin -JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) -JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME +APP_NAME=application-user +JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/$JAR_NAME START_LOG="$REPOSITORY/start.log" ERROR_LOG="$REPOSITORY/error.log" APP_LOG="$REPOSITORY/application.log" $ chmod 666 $START_LOG -$ chmod 666 ERROR_LOG -$ chmod 666 APP_LOG +$ chmod 666 $ERROR_LOG +$ chmod 666 $APP_LOG SERVICE_PID=$(pgrep -f ${APP_NAME}.*.jar) # 실행중인 Spring 서버의 PID NOW=$(date +%c) diff --git a/scripts/stop-user.sh b/scripts/stop-user.sh index 868f77b9..115668ae 100644 --- a/scripts/stop-user.sh +++ b/scripts/stop-user.sh @@ -1,14 +1,14 @@ #!/bin/bash -REPOSITORY=/home/ubuntu/spring-github-action +REPOSITORY="/home/ubuntu/spring-github-action-user" cd $REPOSITORY -APP_NAME=application-admin -JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1) -JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME +APP_NAME=application-user +JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/$JAR_NAME STOP_LOG="$REPOSITORY/stop.log" -$ chmod 666 STOP_LOG +$ chmod 666 "$STOP_LOG" CURRENT_PID=$(pgrep -f ${APP_NAME}.*.jar) From 61f8376422a04c1f70ba1a7a827a272049d7085d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 00:44:34 +0900 Subject: [PATCH 060/685] =?UTF-8?q?chore:=20user=20CI/CD=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 6 ++++-- .github/workflows/deploy-user.yml | 4 +++- .../com/nowait/applicationadmin/ApiAdminApplication.java | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 8e96047b..5d28a479 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -42,7 +42,9 @@ jobs: - name: Set YML run: | mkdir -p application-admin/src/main/resources - echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" | base64 --decode application-admin/src/main/resources/application-admin.yml + echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" \ + | base64 --decode \ + > application-admin/src/main/resources/application-admin.yml find application-admin/src - name: Build Admin JAR @@ -50,7 +52,7 @@ jobs: - name: Prepare deployment package run: | - rm -rf deploy + rm -rf deploy mkdir -p deploy/scripts cp scripts/start-admin.sh scripts/stop-admin.sh deploy/scripts/ cp appspec-admin.yml deploy/appspec.yml diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index 449f6483..04c94183 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -42,7 +42,9 @@ jobs: - name: Set YML run: | mkdir -p application-user/src/main/resources - echo "${{ secrets.APPLICATION_DEV_USER_YML }}" | base64 --decode application-user/src/main/resources/application-user.yml + echo "${{ secrets.APPLICATION_DEV_USER_YML }}" \ + | base64 --decode \ + > application-user/src/main/resources/application-user.yml find application-user/src - name: Build User JAR diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 14ac1764..95f08d3a 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -32,3 +32,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From f4db2768fccc433695fc90e20123d25fdadec7ae Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 00:47:35 +0900 Subject: [PATCH 061/685] =?UTF-8?q?chore:=20user=20CI/CD=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/applicationuser/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java index 7e59d07d..e489c1ab 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java +++ b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java @@ -38,3 +38,4 @@ public static void main(String[] args) { } } + From b7de52aa646463a4cf63a73b3d03514e2a99e672 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 00:50:53 +0900 Subject: [PATCH 062/685] =?UTF-8?q?chore:=20user=20CI/CD=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user.yml | 8 ++++---- .../com/nowait/applicationuser/ApiUserApplication.java | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index 04c94183..f5162197 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -54,11 +54,11 @@ jobs: run: | rm -rf deploy mkdir -p deploy/scripts - cp scripts/start-admin.sh scripts/stop-admin.sh deploy/scripts/ - cp appspec-admin.yml deploy/appspec.yml - cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar + cp scripts/start-user.sh scripts/stop-user.sh deploy/scripts/ + cp appspec-user.yml deploy/appspec.yml + cp application-user/build/libs/application-user-*.jar deploy/application-user.jar cd deploy - zip -r ../NoWait-admin-${GITHUB_SHA}.zip . + zip -r ../NoWait-user-${GITHUB_SHA}.zip . cd .. - name: Configure AWS credentials diff --git a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java index e489c1ab..7e59d07d 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java +++ b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java @@ -38,4 +38,3 @@ public static void main(String[] args) { } } - From fe636df2ffcd28c6344bbd43b1f165d4972bd6af Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 12:14:53 +0900 Subject: [PATCH 063/685] =?UTF-8?q?doc(Menu):=20swagger=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 18 ++++++++++++++++++ .../menu/controller/MenuImageController.java | 18 +++++++++++++++++- .../main/java/com/nowait/menu/entity/Menu.java | 13 +++++++++++++ .../java/com/nowait/menu/entity/MenuImage.java | 1 + 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java index f93eb255..08c6f1bd 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java @@ -17,17 +17,25 @@ import com.nowait.applicationadmin.menu.service.MenuService; import com.nowait.common.api.ApiUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Tag(name = "Menu API", description = "메뉴 API") @RestController @RequestMapping("/admin/menus") @RequiredArgsConstructor +@Slf4j public class MenuController { private final MenuService menuService; @PostMapping("/create") + @Operation(summary = "메뉴 생성", description = "새로운 메뉴를 생성합니다.") + @ApiResponse(responseCode = "201", description = "메뉴 생성") public ResponseEntity createMenu(@Valid @RequestBody MenuCreateRequest request) { MenuCreateResponse response = menuService.createMenu(request); @@ -41,6 +49,8 @@ public ResponseEntity createMenu(@Valid @RequestBody MenuCreateRequest reques } @GetMapping("/all-menus/stores/{storeId}") + @Operation(summary = "가게의 모든 메뉴 조회", description = "특정 가게의 모든 메뉴를 조회") + @ApiResponse(responseCode = "200", description = "가게의 모든 메뉴 조회") public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { return ResponseEntity .status(HttpStatus.OK) @@ -52,6 +62,8 @@ public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { } @GetMapping("/{storeId}/{menuId}") + @Operation(summary = "메뉴 상세 조회", description = "특정 메뉴의 상세 정보를 조회") + @ApiResponse(responseCode = "200", description = "메뉴 상세 조회") public ResponseEntity getMenuById( @PathVariable Long storeId, @PathVariable Long menuId @@ -67,6 +79,8 @@ public ResponseEntity getMenuById( @PatchMapping("/update/{menuId}") + @Operation(summary = "메뉴 수정", description = "특정 메뉴의 정보를 수정합니다.") + @ApiResponse(responseCode = "200", description = "메뉴 수정") public ResponseEntity updateMenu( @PathVariable Long menuId, @Valid @RequestBody MenuUpdateRequest request @@ -81,6 +95,8 @@ public ResponseEntity updateMenu( } @DeleteMapping("/delete/{menuId}") + @Operation(summary = "메뉴 삭제", description = "특정 메뉴를 삭제합니다.") + @ApiResponse(responseCode = "200", description = "메뉴 삭제") public ResponseEntity deleteMenu(@PathVariable Long menuId) { return ResponseEntity .status(HttpStatus.OK) @@ -92,6 +108,8 @@ public ResponseEntity deleteMenu(@PathVariable Long menuId) { } @PatchMapping("/toggle-soldout/{menuId}") + @Operation(summary = "메뉴 판매 중지/재개", description = "특정 메뉴의 판매 상태를 판매 중지 또는 재개로 토글합니다.") + @ApiResponse(responseCode = "200", description = "메뉴 판매 상태 토글") public ResponseEntity toggleSoldOut(@PathVariable Long menuId) { return ResponseEntity .status(HttpStatus.OK) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java index 286b3915..5e5b6d20 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java @@ -14,16 +14,27 @@ import com.nowait.applicationadmin.menu.service.MenuImageService; import com.nowait.common.api.ApiUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Tag(name = "Menu Image API", description = "메뉴 이미지 API") @RestController @RequestMapping("/admin/menus") @RequiredArgsConstructor +@Slf4j public class MenuImageController { private final MenuImageService menuImageService; @PostMapping("/images/{menuId}") + @Operation( + summary = "메뉴 이미지 업로드", + description = "특정 메뉴에 대한 이미지를 업로드합니다. 파일 크기는 최대 10MB로 제한됩니다." + ) + @ApiResponse(responseCode = "201", description = "메뉴 이미지 업로드 성공") public ResponseEntity uploadMenuImage( @PathVariable Long menuId, @RequestParam("file") MultipartFile file @@ -40,11 +51,16 @@ public ResponseEntity uploadMenuImage( } @DeleteMapping("/images/{menuImageId}") + @Operation( + summary = "메뉴 이미지 삭제", + description = "특정 메뉴 이미지 ID에 해당하는 이미지를 삭제합니다." + ) + @ApiResponse(responseCode = "200", description = "메뉴 이미지 삭제 성공") public ResponseEntity deleteMenuImage(@PathVariable Long id) { menuImageService.delete(id); return ResponseEntity .status( - HttpStatus.NO_CONTENT + HttpStatus.OK ) .body( ApiUtils diff --git a/domain-menu/src/main/java/com/nowait/menu/entity/Menu.java b/domain-menu/src/main/java/com/nowait/menu/entity/Menu.java index 31b280ae..f1145543 100644 --- a/domain-menu/src/main/java/com/nowait/menu/entity/Menu.java +++ b/domain-menu/src/main/java/com/nowait/menu/entity/Menu.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import com.nowait.base.BaseTimeEntity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -25,11 +26,23 @@ public class Menu extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long Id; + + @Column(nullable = false) private Long storeId; + + @Column(nullable = false) private String name; + + @Column(nullable = true) private String description; + + @Column(nullable = true) private Integer price; + + @Column(nullable = false) private Boolean isSoldOut; + + @Column(nullable = false) private Boolean deleted; diff --git a/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java b/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java index c7dc602f..b856feab 100644 --- a/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java +++ b/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java @@ -29,6 +29,7 @@ public class MenuImage extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "menu_id") + @Column(nullable = false) private Menu menu; @Column(nullable = false, length = 500) From 523e608ab33fadd7805e60411cc24f5e0c0cb54c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 12:32:56 +0900 Subject: [PATCH 064/685] =?UTF-8?q?doc(Store):=20swagger=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 17 +++++++++++++++++ .../store/controller/StoreImageController.java | 16 +++++++++++++++- .../java/com/nowait/store/entity/Store.java | 12 +++++++----- .../com/nowait/store/entity/StoreImage.java | 1 + 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java index f9ee3a27..07a5e63f 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java @@ -17,17 +17,26 @@ import com.nowait.applicationadmin.store.service.StoreService; import com.nowait.common.api.ApiUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Tag(name = "Store API", description = "주점(Store) API") @RestController @RequestMapping("admin/stores") @RequiredArgsConstructor +@Slf4j public class StoreController { private final StoreService storeService; + // 주점 생성 @PostMapping("/create") + @Operation(summary = "주점 생성", description = "새로운 주점을 생성합니다.") + @ApiResponse(responseCode = "201", description = "주점 생성 성공") public ResponseEntity createStore(@Valid @RequestBody StoreCreateRequest request) { StoreCreateResponse response = storeService.createStore(request); @@ -41,6 +50,8 @@ public ResponseEntity createStore(@Valid @RequestBody StoreCreateRequest requ } @GetMapping("/{storeId}") + @Operation(summary = "주점 조회", description = "주점 ID로 주점을 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점 조회 성공") public ResponseEntity getStoreById(@PathVariable Long storeId) { return ResponseEntity .status(HttpStatus.OK) @@ -52,6 +63,8 @@ public ResponseEntity getStoreById(@PathVariable Long storeId) { } @PatchMapping("/{storeId}") + @Operation(summary = "주점 정보 수정", description = "주점 ID로 주점 정보를 수정합니다.") + @ApiResponse(responseCode = "200", description = "주점 정보 수정 성공") public ResponseEntity updateStore( @PathVariable Long storeId, @Valid @RequestBody StoreUpdateRequest request @@ -66,6 +79,8 @@ public ResponseEntity updateStore( } @DeleteMapping("/{storeId}") + @Operation(summary = "주점 삭제", description = "주점 ID로 주점을 삭제합니다.") + @ApiResponse(responseCode = "200", description = "주점 삭제 성공") public ResponseEntity deleteStore(@PathVariable Long storeId) { return ResponseEntity .ok() @@ -77,6 +92,8 @@ public ResponseEntity deleteStore(@PathVariable Long storeId) { } @PatchMapping("/toggle-active/{storeId}") + @Operation(summary = "주점 활성화/비활성화 토글", description = "주점 ID로 주점의 활성화 상태를 토글합니다.") + @ApiResponse(responseCode = "200", description = "주점 활성화/비활성화 토글 성공") public ResponseEntity toggleActive(@PathVariable Long storeId) { return ResponseEntity .status(HttpStatus.OK) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java index b3547d03..61ed67f2 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java @@ -16,8 +16,12 @@ import com.nowait.applicationadmin.store.service.StoreImageService; import com.nowait.common.api.ApiUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +@Tag(name = "Store Image API", description = "주점 이미지 API") @RestController @RequestMapping("/admin/stores") @RequiredArgsConstructor @@ -26,6 +30,11 @@ public class StoreImageController { private final StoreImageService storeImageService; @PostMapping("/store-images/{storeId}") + @Operation( + summary = "주점 이미지 업로드", + description = "주점에 이미지를 업로드합니다. 최대 10개의 이미지 파일을 업로드할 수 있습니다." + ) + @ApiResponse(responseCode = "201", description = "주점 이미지 업로드 성공") public ResponseEntity uploadStoreImage( @PathVariable Long storeId, @RequestParam("files") List files @@ -56,10 +65,15 @@ public ResponseEntity uploadStoreImage( } @DeleteMapping("/store-images/{storeImageId}") + @Operation( + summary = "주점 이미지 삭제", + description = "주점 이미지를 삭제합니다. 이미지 ID를 사용하여 특정 이미지를 삭제할 수 있습니다." + ) + @ApiResponse(responseCode = "200", description = "주점 이미지 삭제 성공") public ResponseEntity deleteStoreImage(@PathVariable Long imageId) { storeImageService.delete(imageId); return ResponseEntity - .status(HttpStatus.NO_CONTENT) + .status(HttpStatus.OK) .body( ApiUtils .success( diff --git a/domain-store/src/main/java/com/nowait/store/entity/Store.java b/domain-store/src/main/java/com/nowait/store/entity/Store.java index abd03960..c1458feb 100644 --- a/domain-store/src/main/java/com/nowait/store/entity/Store.java +++ b/domain-store/src/main/java/com/nowait/store/entity/Store.java @@ -29,21 +29,23 @@ public class Store extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long storeId; + @Column(name = "department_id", nullable = false) private Long departmentId; + @Column(nullable = false, length = 200) private String name; + @Column(nullable = true, length = 200) private String location; + @Column(nullable = true, length = 200) private String description; - @Builder.Default @Column(name = "is_active", nullable = false) - private Boolean isActive = false; + private Boolean isActive; - @Builder.Default - @Column - private Boolean deleted = false; + @Column(nullable = false) + private Boolean deleted; public Store(LocalDateTime createdAt, Long storeId, Long departmentId, String name, String location, String description, Boolean isActive, Boolean deleted) { diff --git a/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java b/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java index cddb9ad1..41a7d2a7 100644 --- a/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java +++ b/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java @@ -29,6 +29,7 @@ public class StoreImage extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "store_id") + @Column(nullable = false) private Store store; @Column(nullable = false, length = 500) From 61fce6bbf7fea423d16deae27b8bc5c0ed323b85 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 12:33:57 +0900 Subject: [PATCH 065/685] =?UTF-8?q?doc(Store):=20swagger=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/token/controller/TokenController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java b/application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java index ebadba02..be84678e 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java @@ -13,9 +13,13 @@ import com.nowait.applicationadmin.token.service.TokenService; import com.nowait.adminsecurity.auth.jwt.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +@Tag(name = "Token API", description = "토큰 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/refresh-token") @@ -27,7 +31,10 @@ public class TokenController { private long accessTokenExpiration; @Value("${jwt.refresh-token-expiration-ms}") private long refreshTokenExpiration; + @PostMapping + @Operation(summary = "리프레시 토큰", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰과 리프레시 토큰을 발급합니다.") + @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰과 리프레시 토큰 발급 성공") public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ String refreshToken = request.getRefreshToken(); From 179e86bef72e471b2628510d4d136f64e01520ff Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 12:49:32 +0900 Subject: [PATCH 066/685] =?UTF-8?q?fix:=20=EC=97=B0=EA=B4=80=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=EA=B4=80=EB=A0=A8=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/menu/entity/MenuImage.java | 3 +-- .../src/main/java/com/nowait/store/entity/StoreImage.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java b/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java index b856feab..21b312af 100644 --- a/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java +++ b/domain-menu/src/main/java/com/nowait/menu/entity/MenuImage.java @@ -28,8 +28,7 @@ public class MenuImage extends BaseTimeEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "menu_id") - @Column(nullable = false) + @JoinColumn(name = "menu_id", nullable = false) private Menu menu; @Column(nullable = false, length = 500) diff --git a/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java b/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java index 41a7d2a7..d657bd7b 100644 --- a/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java +++ b/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java @@ -28,8 +28,7 @@ public class StoreImage extends BaseTimeEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "store_id") - @Column(nullable = false) + @JoinColumn(name = "store_id", nullable = false) private Store store; @Column(nullable = false, length = 500) From abe54b6e8c92572fa6bc17847c710441910899ed Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 14:22:17 +0900 Subject: [PATCH 067/685] =?UTF-8?q?Doc:=20swagger=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 11 +++++++++++ .../store/controller/StoreController.java | 17 +++++++++++++++++ .../store/service/StoreService.java | 2 +- .../store/service/StoreServiceImpl.java | 3 ++- .../token/controller/TokenController.java | 5 +++++ .../store/repository/StoreRepository.java | 2 +- 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java b/application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java index f7627f2e..b9abf632 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java @@ -10,16 +10,24 @@ import com.nowait.applicationuser.menu.service.MenuService; import com.nowait.common.api.ApiUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Tag(name = "Menu API", description = "메뉴 API") @RestController @RequestMapping("/v1/menus") @RequiredArgsConstructor +@Slf4j public class MenuController { private final MenuService menuService; @GetMapping("/all-menus/stores/{storeId}") + @Operation(summary = "가게의 모든 메뉴 조회", description = "특정 가게의 모든 메뉴를 조회합니다.") + @ApiResponse(responseCode = "200", description = "모든 메뉴를 조회 성공") public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { return ResponseEntity .status(HttpStatus.OK) @@ -31,6 +39,9 @@ public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { } @GetMapping("/{storeId}/{menuId}") + @Operation( + summary = "메뉴 ID로 메뉴 조회", description = "특정 가게의 특정 메뉴를 ID로 조회합니다.") + @ApiResponse(responseCode = "200", description = "메뉴 조회 성공") public ResponseEntity getMenuById( @PathVariable Long storeId, @PathVariable Long menuId diff --git a/application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index f3f7f2c1..29b7287c 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -12,17 +12,25 @@ import com.nowait.applicationuser.store.service.StoreService; import com.nowait.common.api.ApiUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Tag(name = "Store API", description = "주점(Store) API") @RestController @RequestMapping("v1/stores") @RequiredArgsConstructor +@Slf4j public class StoreController { private final StoreService storeService; @GetMapping("/all-stores") + @Operation(summary = "모든 주점 조회", description = "모든 주점을 조회합니다.") + @ApiResponse(responseCode = "200", description = "모든 주점 조회 성공") public ResponseEntity getAllStores() { return ResponseEntity .status(HttpStatus.OK) @@ -34,6 +42,11 @@ public ResponseEntity getAllStores() { } @GetMapping("/all-stores/infinite-scroll") + @Operation( + summary = "모든 주점 페이지네이션 조회", + description = "모든 주점을 페이지네이션으로 조회합니다." + ) + @ApiResponse(responseCode = "200", description = "모든 주점 페이지네이션 조회 성공") public ResponseEntity getAllStores(Pageable pageable) { return ResponseEntity .ok() @@ -41,6 +54,8 @@ public ResponseEntity getAllStores(Pageable pageable) { } @GetMapping("/{storeId}") + @Operation(summary = "주점 ID로 주점 상세 조회", description = "특정 주점을 ID로 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점 상세 조회 성공") public ResponseEntity getStoreById(@PathVariable Long storeId) { return ResponseEntity .status(HttpStatus.OK) @@ -52,6 +67,8 @@ public ResponseEntity getStoreById(@PathVariable Long storeId) { } @GetMapping("/search") + @Operation(summary = "주점 이름으로 주점 검색", description = "주점 이름을 기준으로 주점을 검색합니다.") + @ApiResponse(responseCode = "200", description = "주점 검색 성공") public ResponseEntity searchStores(@RequestParam("name") String name) { return ResponseEntity .ok() diff --git a/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index e2f37f61..f8c854ad 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -11,7 +11,7 @@ public interface StoreService { StoreReadResponse getAllStores(); - public StoreReadResponse getAllStoresByPage(Pageable pageable); + StoreReadResponse getAllStoresByPage(Pageable pageable); StoreReadDto getStoreByStoreId(Long storeId); diff --git a/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 2660af67..e2a1aa09 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -45,9 +45,10 @@ public StoreReadResponse getAllStores() { return StoreReadResponse.of(storeRead, hasNext); } + @Override @Transactional(readOnly = true) public StoreReadResponse getAllStoresByPage(Pageable pageable) { - Slice stores = storeRepository.findAllByDeletedFalseOrderByStoreIdDesc(pageable); + Slice stores = storeRepository.findAllByDeletedFalseOrderByStoreIdAsc(pageable); List storeRead = stores.getContent().stream() .map(store -> { diff --git a/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index bb72e0ec..c9298d04 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -13,6 +13,8 @@ import com.nowait.applicationuser.token.service.TokenService; import com.nowait.externaloauth.jwt.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -27,7 +29,10 @@ public class TokenController { private long accessTokenExpiration; @Value("${jwt.refresh-token-expiration-ms}") private long refreshTokenExpiration; + @PostMapping + @Operation(summary = "리프레시 토큰으로 새로운 액세스 토큰 발급", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급합니다.") + @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰 발급 성공") public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ String refreshToken = request.getRefreshToken(); diff --git a/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java b/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java index 0c3c7013..a0d04ffb 100644 --- a/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java +++ b/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java @@ -19,5 +19,5 @@ public interface StoreRepository extends JpaRepository { List findByNameContainingIgnoreCaseAndDeletedFalse(String name); - Slice findAllByDeletedFalseOrderByStoreIdDesc(Pageable pageable); + Slice findAllByDeletedFalseOrderByStoreIdAsc(Pageable pageable); } From dc1259b8d13802b62d6605eba6b13998ec5a92dc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 14:57:37 +0900 Subject: [PATCH 068/685] =?UTF-8?q?refactor:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/adminsecurity/auth/config/SecurityConfig.java | 3 +-- .../nowait/frontsecurity/auth/config/SecurityConfig.java | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java b/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java index 81214e1c..f41b4913 100644 --- a/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java +++ b/security-admin/src/main/java/com/nowait/adminsecurity/auth/config/SecurityConfig.java @@ -46,8 +46,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(auth -> auth .requestMatchers( "/api/refresh-token", // refresh token (토큰 갱신) - "/admin/menus/**", - "/admin/stores/**", + "/orders/**", "/admin/users/**", "/swagger-ui/**", "/v3/api-docs/**", diff --git a/security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java b/security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java index 8afe5fc2..9d354478 100644 --- a/security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java +++ b/security-front/src/main/java/com/nowait/frontsecurity/auth/config/SecurityConfig.java @@ -59,11 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/oauth2/authorization/kakao", // 카카오 로그인 요청 "/login/oauth2/code/**", // 카카오 인증 콜백 "/api/refresh-token", // refresh token (토큰 갱신) - "orders/**", // 주문 관련 API - "/v1/menus/**", - "/v1/stores/**", - "/api/users/signup", - "/api/users/login", + "/orders/**", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs.json", From bc2c4467fbf38b1d41bdfed6c6fa26cb169bd1ea Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 15:43:40 +0900 Subject: [PATCH 069/685] =?UTF-8?q?refactor:=20cors=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- security-admin/build.gradle | 1 + security-front/build.gradle | 1 + .../auth/jwt/JwtAuthorizationFilter.java | 67 ++++++++++++++++ .../frontsecurity/auth/jwt/JwtUtil.java | 76 +++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java create mode 100644 security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java diff --git a/security-admin/build.gradle b/security-admin/build.gradle index 2a00307c..9c9de646 100644 --- a/security-admin/build.gradle +++ b/security-admin/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation project(':domain-user') implementation project(':domain-base') implementation project(':domain-token') + implementation project(':application-config') // SPRING SECURITY api 'org.springframework.boot:spring-boot-starter-security' api 'org.springframework.boot:spring-boot-starter-test' diff --git a/security-front/build.gradle b/security-front/build.gradle index 5bb9ee1b..6201e438 100644 --- a/security-front/build.gradle +++ b/security-front/build.gradle @@ -21,6 +21,7 @@ dependencies { implementation project(':domain-base') implementation project(':domain-token') implementation project(':external-oauth') + implementation project(':application-config') // SPRING SECURITY api 'org.springframework.boot:spring-boot-starter-security' api 'org.springframework.boot:spring-boot-starter-test' diff --git a/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java b/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java new file mode 100644 index 00000000..3969108e --- /dev/null +++ b/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java @@ -0,0 +1,67 @@ +package com.nowait.frontsecurity.auth.jwt; + +import java.io.IOException; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; +import com.nowait.externaloauth.service.CustomUserDetailService; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RequiredArgsConstructor +@Slf4j +public class JwtAuthorizationFilter extends OncePerRequestFilter { + private final JwtUtil jwtUtil; + private final CustomUserDetailService userDetailsService; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + try { + String token = extractTokenFromRequest(request); + if (token != null) { + // 만료 체크 + if (jwtUtil.isExpired(token)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().print("access token expired"); + return; + } + // 토큰 category 체크(불필요하면 생략) + String tokenCategory = jwtUtil.getTokenCategory(token); + if (!"accessToken".equals(tokenCategory)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().print("invalid access token"); + return; + } + // userId 추출 → UserDetails 조회 + Long userId = jwtUtil.getUserId(token); + var userDetails = userDetailsService.loadUserById(userId); + // 인증 객체 생성 및 컨텍스트에 설정 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + } catch (Exception ex) { + log.error("JWT filter error: {}", ex.getMessage()); + } finally { + filterChain.doFilter(request, response); + } + } + + private String extractTokenFromRequest(HttpServletRequest request) { + String header = request.getHeader("Authorization"); + if (header != null && header.startsWith("Bearer ")) { + return header.substring(7); + } + return null; + } +} diff --git a/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java b/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java new file mode 100644 index 00000000..9c41194e --- /dev/null +++ b/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java @@ -0,0 +1,76 @@ +package com.nowait.frontsecurity.auth.jwt; + +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Jwts; + +@Component +public class JwtUtil { + private final SecretKey secretKey; + + // 시크릿 키를 암호화하여, 키 생성 + public JwtUtil(@Value("${jwt.secret}") String secret) { + this.secretKey = new SecretKeySpec( + secret.getBytes(StandardCharsets.UTF_8), + Jwts.SIG.HS256.key().build().getAlgorithm() + ); + } + + public String createAccessToken(String tokenCategory, Long userId, String role, Long expiredMs) { + return Jwts.builder() + .claim("tokenCategory", tokenCategory) // accessToken + .claim("userId", userId) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } + + public String createRefreshToken(String tokenCategory, Long userId, Long expiredMs) { + return Jwts.builder() + .claim("tokenCategory", tokenCategory) // refreshToken + .claim("userId", userId) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } + + public String getTokenCategory(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseClaimsJws(token) + .getBody() + .get("tokenCategory", String.class); + } + + public String getRole(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseClaimsJws(token) + .getBody() + .get("role", String.class); + } + + public Long getUserId(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseClaimsJws(token) + .getBody() + .get("userId", Long.class); + } + + public Boolean isExpired(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseSignedClaims(token) + .getPayload() + .getExpiration() + .before(new Date()); + } + +} From f2f60cc71f44f5d54a7c7a59873d385449201b36 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 15:48:41 +0900 Subject: [PATCH 070/685] =?UTF-8?q?refactor:=20jwt=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/jwt/JwtAuthorizationFilter.java | 67 ---------------- .../frontsecurity/auth/jwt/JwtUtil.java | 76 ------------------- 2 files changed, 143 deletions(-) delete mode 100644 security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java delete mode 100644 security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java diff --git a/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java b/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java deleted file mode 100644 index 3969108e..00000000 --- a/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtAuthorizationFilter.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.nowait.frontsecurity.auth.jwt; - -import java.io.IOException; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; -import com.nowait.externaloauth.service.CustomUserDetailService; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@RequiredArgsConstructor -@Slf4j -public class JwtAuthorizationFilter extends OncePerRequestFilter { - private final JwtUtil jwtUtil; - private final CustomUserDetailService userDetailsService; - - @Override - protected void doFilterInternal( - HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain - ) throws ServletException, IOException { - try { - String token = extractTokenFromRequest(request); - if (token != null) { - // 만료 체크 - if (jwtUtil.isExpired(token)) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().print("access token expired"); - return; - } - // 토큰 category 체크(불필요하면 생략) - String tokenCategory = jwtUtil.getTokenCategory(token); - if (!"accessToken".equals(tokenCategory)) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().print("invalid access token"); - return; - } - // userId 추출 → UserDetails 조회 - Long userId = jwtUtil.getUserId(token); - var userDetails = userDetailsService.loadUserById(userId); - // 인증 객체 생성 및 컨텍스트에 설정 - UsernamePasswordAuthenticationToken authenticationToken = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authenticationToken); - } - } catch (Exception ex) { - log.error("JWT filter error: {}", ex.getMessage()); - } finally { - filterChain.doFilter(request, response); - } - } - - private String extractTokenFromRequest(HttpServletRequest request) { - String header = request.getHeader("Authorization"); - if (header != null && header.startsWith("Bearer ")) { - return header.substring(7); - } - return null; - } -} diff --git a/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java b/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java deleted file mode 100644 index 9c41194e..00000000 --- a/security-front/src/main/java/com/nowait/frontsecurity/auth/jwt/JwtUtil.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.nowait.frontsecurity.auth.jwt; - -import java.nio.charset.StandardCharsets; -import java.util.Date; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import io.jsonwebtoken.Jwts; - -@Component -public class JwtUtil { - private final SecretKey secretKey; - - // 시크릿 키를 암호화하여, 키 생성 - public JwtUtil(@Value("${jwt.secret}") String secret) { - this.secretKey = new SecretKeySpec( - secret.getBytes(StandardCharsets.UTF_8), - Jwts.SIG.HS256.key().build().getAlgorithm() - ); - } - - public String createAccessToken(String tokenCategory, Long userId, String role, Long expiredMs) { - return Jwts.builder() - .claim("tokenCategory", tokenCategory) // accessToken - .claim("userId", userId) - .claim("role", role) - .issuedAt(new Date(System.currentTimeMillis())) - .expiration(new Date(System.currentTimeMillis() + expiredMs)) - .signWith(secretKey) - .compact(); - } - - public String createRefreshToken(String tokenCategory, Long userId, Long expiredMs) { - return Jwts.builder() - .claim("tokenCategory", tokenCategory) // refreshToken - .claim("userId", userId) - .issuedAt(new Date(System.currentTimeMillis())) - .expiration(new Date(System.currentTimeMillis() + expiredMs)) - .signWith(secretKey) - .compact(); - } - - public String getTokenCategory(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseClaimsJws(token) - .getBody() - .get("tokenCategory", String.class); - } - - public String getRole(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseClaimsJws(token) - .getBody() - .get("role", String.class); - } - - public Long getUserId(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseClaimsJws(token) - .getBody() - .get("userId", Long.class); - } - - public Boolean isExpired(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseSignedClaims(token) - .getPayload() - .getExpiration() - .before(new Date()); - } - -} From 876068872f05147255c5b27a0cee1ad715d270d4 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 1 Jul 2025 16:23:19 +0900 Subject: [PATCH 071/685] =?UTF-8?q?refactor(APIAdmin,APIUser):=20cors=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application-admin/build.gradle | 1 + .../java/com/nowait/applicationadmin/ApiAdminApplication.java | 3 ++- .../src/main/java/com/nowait/config/config/CorsConfig.java | 2 +- application-user/build.gradle | 1 + .../java/com/nowait/applicationuser/ApiUserApplication.java | 4 ++-- .../applicationuser/order/dto/OrderCreateResponseDto.java | 2 ++ 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/application-admin/build.gradle b/application-admin/build.gradle index 31009a7f..43708f07 100644 --- a/application-admin/build.gradle +++ b/application-admin/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation project(':domain-reservation') // 예약 관련 도메인 implementation project(':security-admin') // 인증 관련 도메인 implementation project(':infra-aws') // aws 관련 도메인 + implementation project(':application-config') // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 95f08d3a..4099bf40 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -9,7 +9,8 @@ @SpringBootApplication(scanBasePackages = { "com.nowait.applicationadmin", "com.nowait.infraaws", - "com.nowait.adminsecurity" + "com.nowait.adminsecurity", + "com.nowait.config" }) @EntityScan(basePackages = { "com.nowait.menu.entity", diff --git a/application-config/src/main/java/com/nowait/config/config/CorsConfig.java b/application-config/src/main/java/com/nowait/config/config/CorsConfig.java index 06823454..f86c679d 100644 --- a/application-config/src/main/java/com/nowait/config/config/CorsConfig.java +++ b/application-config/src/main/java/com/nowait/config/config/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173","http://localhost:63342")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 diff --git a/application-user/build.gradle b/application-user/build.gradle index 6b68d7be..cfa67b69 100644 --- a/application-user/build.gradle +++ b/application-user/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation project(':infra-aws') // 사용자 관련 도메인 implementation project(':security-front') // 사용자 관련 도메인 implementation project(':external-oauth') // 외부 OAuth 관련 도메인 + implementation project(':application-config') // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java index 7e59d07d..fcfb659f 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java +++ b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java @@ -10,7 +10,8 @@ @SpringBootApplication(scanBasePackages = { "com.nowait.applicationuser", "com.nowait.frontsecurity", - "com.nowait.externaloauth" + "com.nowait.externaloauth", + "com.nowait.config" }) @EntityScan(basePackages = { "com.nowait.menu.entity", @@ -19,7 +20,6 @@ "com.nowait.user.entity", "com.nowait.bookmark.entity", "com.nowait.reservation.entity", - "com.nowait.order.entity", "com.nowait.order.entity" }) @EnableJpaRepositories(basePackages = { diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index cd8f6568..3326d510 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -15,6 +15,7 @@ public class OrderCreateResponseDto { private Long orderId; // 주문 ID // 주문 상태 (예: "주문완료", "배송중" 등) private Long storeId; // 상점 ID private String storeName; // 상점 이름 + private String sessionId; private List orderItems; // 주문 항목 목록 public static OrderCreateResponseDto fromEntity(UserOrder order) { @@ -22,6 +23,7 @@ public static OrderCreateResponseDto fromEntity(UserOrder order) { .orderId(order.getId()) .storeId(order.getStore().getStoreId()) .storeName(order.getStore().getName()) + .sessionId(order.getSessionId()) .orderItems(List.of()) .build(); } From 3cbfb70e0579899fb2338bde207def0835bf63db Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 17:13:31 +0900 Subject: [PATCH 072/685] =?UTF-8?q?feat:=20store,=20menu=20custom=20except?= =?UTF-8?q?ion=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/ApiAdminApplication.java | 3 ++- .../menu/service/MenuImageService.java | 10 ++++++++-- .../menu/service/MenuService.java | 14 ++++++++++---- .../ReservationNotFoundException.java | 9 --------- .../service/ReservationService.java | 1 - .../store/service/StoreImageService.java | 11 +++++++---- .../store/service/StoreServiceImpl.java | 18 +++++++++++++++--- .../applicationuser/ApiUserApplication.java | 4 ++-- .../menu/service/MenuService.java | 11 ++++++++++- .../store/service/StoreServiceImpl.java | 10 +++++++++- .../nowait/common/exception/ErrorMessage.java | 17 ++++++++++++++++- .../exception/MenuImageEmptyException.java | 9 +++++++++ .../exception/MenuImageNotFoundException.java | 9 +++++++++ .../menu/exception/MenuNotFoundException.java | 9 +++++++++ .../exception/MenuParamEmptyException.java | 9 +++++++++ .../exception/StoreImageEmptyException.java | 9 +++++++++ .../exception/StoreImageNotFoundException.java | 9 +++++++++ .../exception/StoreKeywordEmptyException.java | 9 +++++++++ .../exception/StoreNotFoundException.java | 9 +++++++++ .../exception/StoreParamEmptyException.java | 9 +++++++++ 20 files changed, 160 insertions(+), 29 deletions(-) delete mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java create mode 100644 domain-menu/src/main/java/com/nowait/menu/exception/MenuImageEmptyException.java create mode 100644 domain-menu/src/main/java/com/nowait/menu/exception/MenuImageNotFoundException.java create mode 100644 domain-menu/src/main/java/com/nowait/menu/exception/MenuNotFoundException.java create mode 100644 domain-menu/src/main/java/com/nowait/menu/exception/MenuParamEmptyException.java create mode 100644 domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java create mode 100644 domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java create mode 100644 domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java create mode 100644 domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java create mode 100644 domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 95f08d3a..4099bf40 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -9,7 +9,8 @@ @SpringBootApplication(scanBasePackages = { "com.nowait.applicationadmin", "com.nowait.infraaws", - "com.nowait.adminsecurity" + "com.nowait.adminsecurity", + "com.nowait.config" }) @EntityScan(basePackages = { "com.nowait.menu.entity", diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java index bc884793..ba1a7b77 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java @@ -8,6 +8,9 @@ import com.nowait.infraaws.s3.S3Service; import com.nowait.menu.entity.Menu; import com.nowait.menu.entity.MenuImage; +import com.nowait.menu.exception.MenuImageEmptyException; +import com.nowait.menu.exception.MenuImageNotFoundException; +import com.nowait.menu.exception.MenuNotFoundException; import com.nowait.menu.repository.MenuImageRepository; import com.nowait.menu.repository.MenuRepository; @@ -23,10 +26,13 @@ public class MenuImageService { @Transactional public MenuImageUploadResponse save(Long menuId, MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new MenuImageEmptyException(); + } String type = "menu"; Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + .orElseThrow(MenuNotFoundException::new); S3Service.S3UploadResult uploadResult = s3Service.upload(type, menuId, file).join(); @@ -46,7 +52,7 @@ public MenuImageUploadResponse save(Long menuId, MultipartFile file) { @Transactional public void delete(Long id) { MenuImage menuImage = menuImageRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("MenuImage not found with id: " + id)); + .orElseThrow(MenuImageNotFoundException::new); s3Service.delete(menuImage.getFileKey()); menuImageRepository.delete(menuImage); diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index d5afc825..a0c25374 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -13,8 +13,10 @@ import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; import com.nowait.menu.entity.Menu; import com.nowait.menu.entity.MenuImage; +import com.nowait.menu.exception.MenuNotFoundException; import com.nowait.menu.repository.MenuImageRepository; import com.nowait.menu.repository.MenuRepository; +import com.nowait.order.exception.OrderParameterEmptyException; import lombok.RequiredArgsConstructor; @@ -53,8 +55,12 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { @Transactional(readOnly = true) public MenuReadDto getMenuById(Long storeId, Long menuId) { + if (storeId == null || menuId == null) { + throw new OrderParameterEmptyException(); + } + Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) - .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + .orElseThrow(MenuNotFoundException::new); List images = menuImageRepository.findByMenu(menu); List imageDto = images.stream() @@ -68,7 +74,7 @@ public MenuReadDto getMenuById(Long storeId, Long menuId) { @Transactional public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { Menu menu = menuRepository.findByIdAndDeletedFalse(menuId) - .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + .orElseThrow(MenuNotFoundException::new); menu.updateInfo( request.getName(), @@ -89,7 +95,7 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { @Transactional public String deleteMenu(Long menuId) { Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new IllegalArgumentException("Menu is already deleted with id: " + menuId)); + .orElseThrow(MenuNotFoundException::new); menu.markAsDeleted(); menuRepository.save(menu); @@ -100,7 +106,7 @@ public String deleteMenu(Long menuId) { @Transactional public Boolean toggleSoldOut(Long menuId) { Menu menu = menuRepository.findById(menuId) - .orElseThrow(() -> new IllegalArgumentException("해당 메뉴가 존재하지 않습니다.")); + .orElseThrow(MenuNotFoundException::new); menu.toggleSoldOut(); menuRepository.save(menu); diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java deleted file mode 100644 index 491cc3f6..00000000 --- a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/exception/ReservationNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nowait.applicationadmin.reservation.exception; - -import com.nowait.common.exception.ErrorMessage; - -public class ReservationNotFoundException extends RuntimeException { - public ReservationNotFoundException() { - super(ErrorMessage.NOTFOUND_RESERVATION.getMessage()); - } -} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index ee9831e9..6b3b7a2b 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -10,7 +10,6 @@ import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; -import com.nowait.applicationadmin.reservation.exception.ReservationNotFoundException; import com.nowait.reservation.entity.Reservation; import com.nowait.reservation.repository.ReservationRepository; import com.nowait.common.enums.ReservationStatus; diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java index f0b5e833..8cb92dde 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java @@ -12,6 +12,9 @@ import com.nowait.store.entity.Store; import com.nowait.store.entity.StoreImage; +import com.nowait.store.exception.StoreImageEmptyException; +import com.nowait.store.exception.StoreImageNotFoundException; +import com.nowait.store.exception.StoreNotFoundException; import com.nowait.store.repository.StoreImageRepository; import com.nowait.store.repository.StoreRepository; import com.nowait.infraaws.s3.S3Service; @@ -29,10 +32,11 @@ public class StoreImageService { @Transactional public List saveAll(Long storeId, List files) { + if (files == null || files.isEmpty()) throw new StoreImageEmptyException(); String type = "store"; Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new EntityNotFoundException("Store not found with id: " + storeId)); + .orElseThrow(StoreNotFoundException::new); // 모든 파일을 비동기로 업로드 List> uploadFutures = new ArrayList<>(); @@ -52,8 +56,7 @@ public List saveAll(Long storeId, List // DB 저장은 모든 S3 업로드 성공 후 수행 List imageUploadResponses = new ArrayList<>(); - for (int i = 0; i < uploadResults.size(); i++) { - S3Service.S3UploadResult uploadResult = uploadResults.get(i); + for (S3Service.S3UploadResult uploadResult : uploadResults) { StoreImage storeImage = StoreImage.builder() .store(store) .imageUrl(uploadResult.url()) @@ -70,7 +73,7 @@ public List saveAll(Long storeId, List @Transactional public void delete(Long storeImageId) { StoreImage storeImage = storeImageRepository.findById(storeImageId) - .orElseThrow(() -> new EntityNotFoundException("StoreImage not found with id: " + storeImageId)); + .orElseThrow(StoreImageNotFoundException::new); s3Service.delete(storeImage.getFileKey()); storeImageRepository.delete(storeImage); diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index 56b6406b..196f634f 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -12,6 +12,8 @@ import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; import com.nowait.store.entity.Store; import com.nowait.store.entity.StoreImage; +import com.nowait.store.exception.StoreNotFoundException; +import com.nowait.store.exception.StoreParamEmptyException; import com.nowait.store.repository.StoreImageRepository; import com.nowait.store.repository.StoreRepository; @@ -28,6 +30,8 @@ public class StoreServiceImpl implements StoreService { @Override @Transactional public StoreCreateResponse createStore(StoreCreateRequest request) { + if (request == null) throw new StoreParamEmptyException(); + Store toSave = request.toEntity(); Store saved = storeRepository.save(toSave); @@ -38,8 +42,10 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { @Override @Transactional(readOnly = true) public StoreReadDto getStoreByStoreId(Long storeId) { + if (storeId == null) throw new StoreParamEmptyException(); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + .orElseThrow(StoreNotFoundException::new); List images = storeImageRepository.findByStore(store); List imageDto = images.stream() @@ -52,8 +58,10 @@ public StoreReadDto getStoreByStoreId(Long storeId) { @Override @Transactional public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request) { + if (storeId == null || request == null) throw new StoreParamEmptyException(); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + .orElseThrow(StoreNotFoundException::new); store.updateInfo( request.getName(), @@ -74,8 +82,12 @@ public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request) { @Override @Transactional public String deleteStore(Long storeId) { + if (storeId == null) { + throw new StoreParamEmptyException(); + } + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + .orElseThrow(StoreNotFoundException::new); store.markAsDeleted(); storeRepository.save(store); diff --git a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java index 7e59d07d..fcfb659f 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java +++ b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java @@ -10,7 +10,8 @@ @SpringBootApplication(scanBasePackages = { "com.nowait.applicationuser", "com.nowait.frontsecurity", - "com.nowait.externaloauth" + "com.nowait.externaloauth", + "com.nowait.config" }) @EntityScan(basePackages = { "com.nowait.menu.entity", @@ -19,7 +20,6 @@ "com.nowait.user.entity", "com.nowait.bookmark.entity", "com.nowait.reservation.entity", - "com.nowait.order.entity", "com.nowait.order.entity" }) @EnableJpaRepositories(basePackages = { diff --git a/application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java b/application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index bd28fada..f9309c3e 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -10,6 +10,8 @@ import com.nowait.applicationuser.menu.dto.MenuReadResponse; import com.nowait.menu.entity.Menu; import com.nowait.menu.entity.MenuImage; +import com.nowait.menu.exception.MenuNotFoundException; +import com.nowait.menu.exception.MenuParamEmptyException; import com.nowait.menu.repository.MenuImageRepository; import com.nowait.menu.repository.MenuRepository; @@ -25,6 +27,9 @@ public class MenuService { @Transactional(readOnly = true) public MenuReadResponse getAllMenusByStoreId(Long storeId) { + if (storeId == null) { + throw new MenuParamEmptyException(); + } List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); List menuReadResponse = menus.stream() @@ -42,8 +47,12 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { @Transactional(readOnly = true) public MenuReadDto getMenuById(Long storeId, Long menuId) { + if (storeId == null || menuId == null) { + throw new MenuParamEmptyException(); + } + Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) - .orElseThrow(() -> new IllegalArgumentException("Menu not found with id: " + menuId)); + .orElseThrow(MenuNotFoundException::new); List images = menuImageRepository.findByMenu(menu); List imageDto = images.stream() diff --git a/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index e2a1aa09..14cb28fe 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/application-user/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -11,6 +11,8 @@ import com.nowait.applicationuser.store.dto.StoreReadResponse; import com.nowait.store.entity.Store; import com.nowait.store.entity.StoreImage; +import com.nowait.store.exception.StoreNotFoundException; +import com.nowait.store.exception.StoreParamEmptyException; import com.nowait.store.repository.StoreImageRepository; import com.nowait.store.repository.StoreRepository; @@ -68,8 +70,10 @@ public StoreReadResponse getAllStoresByPage(Pageable pageable) { @Override @Transactional(readOnly = true) public StoreReadDto getStoreByStoreId(Long storeId) { + if (storeId == null) throw new StoreParamEmptyException(); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + .orElseThrow(StoreNotFoundException::new); List images = storeImageRepository.findByStore(store); List imageDto = images.stream() @@ -81,6 +85,10 @@ public StoreReadDto getStoreByStoreId(Long storeId) { @Override public List searchStoresByName(String name) { + if (name == null || name.isBlank()) { + throw new StoreParamEmptyException(); + } + List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(name); return stores.stream() .map(store -> { diff --git a/common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/common/src/main/java/com/nowait/common/exception/ErrorMessage.java index d695104f..5fb5aecb 100644 --- a/common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -24,7 +24,22 @@ public enum ErrorMessage { // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), - NOT_OWN_BOOKMARK("해당 주점은 다른 사용자가 북마크한 주점입니다.", "bookmark002"); + NOT_OWN_BOOKMARK("해당 주점은 다른 사용자가 북마크한 주점입니다.", "bookmark002"), + + // menu + MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"), + MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu001"), + + // store + STORE_PARAMETER_EMPTY("주점 생성 시 파라미터 정보가 없습니다.", "store001"), + STORE_NOT_FOUND("해당 주점을 찾을 수 없습니다.", "store002"), + + // image + IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), + IMAGE_FILE_NOT_FOUND("이미지 파일을 업로드 해주세요", "image001"), + + // search + SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"); private final String message; private final String code; diff --git a/domain-menu/src/main/java/com/nowait/menu/exception/MenuImageEmptyException.java b/domain-menu/src/main/java/com/nowait/menu/exception/MenuImageEmptyException.java new file mode 100644 index 00000000..bd0c146f --- /dev/null +++ b/domain-menu/src/main/java/com/nowait/menu/exception/MenuImageEmptyException.java @@ -0,0 +1,9 @@ +package com.nowait.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuImageEmptyException extends RuntimeException { + public MenuImageEmptyException() { + super(ErrorMessage.IMAGE_FILE_EMPTY.getMessage()); + } +} diff --git a/domain-menu/src/main/java/com/nowait/menu/exception/MenuImageNotFoundException.java b/domain-menu/src/main/java/com/nowait/menu/exception/MenuImageNotFoundException.java new file mode 100644 index 00000000..87726f72 --- /dev/null +++ b/domain-menu/src/main/java/com/nowait/menu/exception/MenuImageNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuImageNotFoundException extends RuntimeException { + public MenuImageNotFoundException() { + super(ErrorMessage.IMAGE_FILE_NOT_FOUND.getMessage()); + } +} diff --git a/domain-menu/src/main/java/com/nowait/menu/exception/MenuNotFoundException.java b/domain-menu/src/main/java/com/nowait/menu/exception/MenuNotFoundException.java new file mode 100644 index 00000000..b119fe65 --- /dev/null +++ b/domain-menu/src/main/java/com/nowait/menu/exception/MenuNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuNotFoundException extends RuntimeException { + public MenuNotFoundException() { + super(ErrorMessage.MENU_NOT_FOUND.getMessage()); + } +} diff --git a/domain-menu/src/main/java/com/nowait/menu/exception/MenuParamEmptyException.java b/domain-menu/src/main/java/com/nowait/menu/exception/MenuParamEmptyException.java new file mode 100644 index 00000000..d743cd6c --- /dev/null +++ b/domain-menu/src/main/java/com/nowait/menu/exception/MenuParamEmptyException.java @@ -0,0 +1,9 @@ +package com.nowait.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuParamEmptyException extends RuntimeException { + public MenuParamEmptyException() { + super(ErrorMessage.MENU_PARAMETER_EMPTY.getMessage()); + } +} diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java b/domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java new file mode 100644 index 00000000..0f65bb1e --- /dev/null +++ b/domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java @@ -0,0 +1,9 @@ +package com.nowait.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreImageEmptyException extends RuntimeException { + public StoreImageEmptyException() { + super(ErrorMessage.IMAGE_FILE_EMPTY.getMessage()); + } +} diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java b/domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java new file mode 100644 index 00000000..aff9e0c0 --- /dev/null +++ b/domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreImageNotFoundException extends RuntimeException { + public StoreImageNotFoundException() { + super(ErrorMessage.IMAGE_FILE_NOT_FOUND.getMessage()); + } +} diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java b/domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java new file mode 100644 index 00000000..2b1fecc7 --- /dev/null +++ b/domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java @@ -0,0 +1,9 @@ +package com.nowait.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreKeywordEmptyException extends RuntimeException { + public StoreKeywordEmptyException() { + super(ErrorMessage.STORE_PARAMETER_EMPTY.getMessage()); + } +} diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java b/domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java new file mode 100644 index 00000000..15d485db --- /dev/null +++ b/domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreNotFoundException extends RuntimeException { + public StoreNotFoundException() { + super(ErrorMessage.STORE_NOT_FOUND.getMessage()); + } +} diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java b/domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java new file mode 100644 index 00000000..5dc0bf13 --- /dev/null +++ b/domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java @@ -0,0 +1,9 @@ +package com.nowait.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreParamEmptyException extends RuntimeException { + public StoreParamEmptyException() { + super(ErrorMessage.STORE_PARAMETER_EMPTY.getMessage()); + } +} From a0b620598c8f1d404bb1c5c012e7daa0d6f674b7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 17:20:14 +0900 Subject: [PATCH 073/685] =?UTF-8?q?refactor:=20reservation=20Exception=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 2 +- .../reservation/service/ReservationService.java | 1 + .../exception/ReservationNotFoundException.java | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 2ccd5726..86667a64 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartException; -import com.nowait.applicationadmin.reservation.exception.ReservationNotFoundException; +import com.nowait.reservation.exception.ReservationNotFoundException; import com.nowait.token.exception.BusinessException; import com.nowait.adminsecurity.exception.ResourceNotFoundException; import com.nowait.adminsecurity.exception.UnauthorizedException; diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 6b3b7a2b..07109a97 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -11,6 +11,7 @@ import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; import com.nowait.reservation.entity.Reservation; +import com.nowait.reservation.exception.ReservationNotFoundException; import com.nowait.reservation.repository.ReservationRepository; import com.nowait.common.enums.ReservationStatus; diff --git a/domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java b/domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java new file mode 100644 index 00000000..3f65976f --- /dev/null +++ b/domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java @@ -0,0 +1,10 @@ +package com.nowait.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationNotFoundException extends RuntimeException { + public ReservationNotFoundException() { + super(ErrorMessage.NOTFOUND_RESERVATION.getMessage()); + } +} + From 40ae847cd30d4f162f17faeb27304ab4afc55805 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 1 Jul 2025 17:41:40 +0900 Subject: [PATCH 074/685] =?UTF-8?q?feat(APIAdmin,Order):=20order=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order에 입금자명 추가 - reservation exception 디렉토리 위치 변경 --- .../com/nowait/applicationadmin/ApiAdminApplication.java | 3 ++- .../reservation/service/ReservationService.java | 2 +- .../exception/GlobalExceptionHandler.java | 5 ++--- .../applicationuser/order/dto/OrderCreateRequestDto.java | 1 + .../order/dto/OrderCreateResponseDto.java | 2 ++ .../applicationuser/order/service/OrderService.java | 1 + .../exception/ReservationNotFoundException.java | 9 --------- .../src/main/java/com/nowait/order/entity/UserOrder.java | 2 ++ 8 files changed, 11 insertions(+), 14 deletions(-) delete mode 100644 application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java index 4099bf40..0e8b5c2f 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java @@ -10,7 +10,8 @@ "com.nowait.applicationadmin", "com.nowait.infraaws", "com.nowait.adminsecurity", - "com.nowait.config" + "com.nowait.config", + "com.nowait.reservation" }) @EntityScan(basePackages = { "com.nowait.menu.entity", diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 07109a97..79b891d7 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -10,10 +10,10 @@ import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; +import com.nowait.common.enums.ReservationStatus; import com.nowait.reservation.entity.Reservation; import com.nowait.reservation.exception.ReservationNotFoundException; import com.nowait.reservation.repository.ReservationRepository; -import com.nowait.common.enums.ReservationStatus; import lombok.RequiredArgsConstructor; diff --git a/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index bcf4eda3..14039ab8 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -1,8 +1,8 @@ package com.nowait.applicationuser.exception; import static com.nowait.common.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.UNAUTHORIZED; +import static org.springframework.http.HttpStatus.*; import java.util.Map; import java.util.stream.Collectors; @@ -17,8 +17,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartException; - -import com.nowait.applicationuser.reservation.exception.ReservationNotFoundException; import com.nowait.bookmark.exception.BookmarkOwnerMismatchException; import com.nowait.bookmark.exception.DuplicateBookmarkException; import com.nowait.common.exception.ErrorMessage; @@ -28,6 +26,7 @@ import com.nowait.order.exception.DuplicateOrderException; import com.nowait.order.exception.OrderItemsEmptyException; import com.nowait.order.exception.OrderParameterEmptyException; +import com.nowait.reservation.exception.ReservationNotFoundException; import com.nowait.token.exception.BusinessException; import com.nowait.user.exception.UserNotFoundException; diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java index 1372a980..f480ed05 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java @@ -10,6 +10,7 @@ @AllArgsConstructor @Builder public class OrderCreateRequestDto { + private final String depositorName; // 예약자 이름 private final List items; // 장바구니 항목 리스트 } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index 3326d510..89a0e5e5 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -16,6 +16,7 @@ public class OrderCreateResponseDto { private Long storeId; // 상점 ID private String storeName; // 상점 이름 private String sessionId; + private String depositorName; private List orderItems; // 주문 항목 목록 public static OrderCreateResponseDto fromEntity(UserOrder order) { @@ -24,6 +25,7 @@ public static OrderCreateResponseDto fromEntity(UserOrder order) { .storeId(order.getStore().getStoreId()) .storeName(order.getStore().getName()) .sessionId(order.getSessionId()) + .depositorName(order.getDepositorName()) .orderItems(List.of()) .build(); } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 07cc19c3..10db847f 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -55,6 +55,7 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId, .store(store) .signature(signature) // signature 저장 .sessionId(sessionId) // sessionId 저장 + .depositorName(orderCreateRequestDto.getDepositorName()) .build(); UserOrder savedOrder = orderRepository.save(order); diff --git a/application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java b/application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java deleted file mode 100644 index bb6db2f8..00000000 --- a/application-user/src/main/java/com/nowait/applicationuser/reservation/exception/ReservationNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nowait.applicationuser.reservation.exception; - -import com.nowait.common.exception.ErrorMessage; - -public class ReservationNotFoundException extends RuntimeException { - public ReservationNotFoundException() { - super(ErrorMessage.NOTFOUND_RESERVATION.getMessage()); - } -} diff --git a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java index 7d94e44e..4228c290 100644 --- a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java +++ b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java @@ -47,4 +47,6 @@ public class UserOrder extends BaseTimeEntity { private String sessionId; + private String depositorName; + } From 7a43ff7c1541dc8d9cf0bd8ad84e3e511624708c Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 1 Jul 2025 18:05:40 +0900 Subject: [PATCH 075/685] =?UTF-8?q?feat(APIAdmin,Order):=20order=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order에 입금자명 추가 - reservation exception 디렉토리 위치 변경 --- .../exception/GlobalExceptionHandler.java | 8 ++++++++ .../order/dto/OrderCreateRequestDto.java | 4 ++++ .../applicationuser/order/service/OrderService.java | 10 ++++++++-- .../java/com/nowait/common/exception/ErrorMessage.java | 1 + .../main/java/com/nowait/order/entity/UserOrder.java | 2 +- .../order/exception/DepositorNameTooLongException.java | 9 +++++++++ 6 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java diff --git a/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 14039ab8..20d346c1 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/application-user/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -23,6 +23,7 @@ import com.nowait.common.exception.ErrorResponse; import com.nowait.frontsecurity.exception.ResourceNotFoundException; import com.nowait.frontsecurity.exception.UnauthorizedException; +import com.nowait.order.exception.DepositorNameTooLongException; import com.nowait.order.exception.DuplicateOrderException; import com.nowait.order.exception.OrderItemsEmptyException; import com.nowait.order.exception.OrderParameterEmptyException; @@ -139,6 +140,13 @@ public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e) { return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); } + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(DepositorNameTooLongException.class) + public ErrorResponse depositorNameTooLongException(DepositorNameTooLongException e) { + log.error("depositorNameTooLongException", e); + return new ErrorResponse(e.getMessage(), DEPOSITOR_NAME_TOO_LONG.getCode()); + } + @ResponseStatus(value = BAD_REQUEST) @ExceptionHandler(DuplicateOrderException.class) public ErrorResponse duplicateOrderException(DuplicateOrderException e) { diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java index f480ed05..1bdd2528 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java @@ -2,6 +2,8 @@ import java.util.List; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -11,6 +13,8 @@ @Builder public class OrderCreateRequestDto { private final String depositorName; // 예약자 이름 + @NotBlank(message = "주문자 이름은 필수입니다") + @Size(max = 100, message = "주문자 이름은 10자 이하여야 합니다") private final List items; // 장바구니 항목 리스트 } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 10db847f..35ffdde4 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -103,10 +103,16 @@ public List getOrderItems(Long storeId, Long tableI private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { if (storeId == null || tableId == null || orderCreateRequestDto == null) { - throw new OrderParameterEmptyException(); + throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getItems() == null || orderCreateRequestDto.getItems().isEmpty()) { - throw new OrderItemsEmptyException(); + throw new OrderItemsEmptyException(); + } + if (orderCreateRequestDto.getDepositorName() == null || orderCreateRequestDto.getDepositorName().trim().isEmpty()) { + throw new OrderParameterEmptyException(); + } + if (orderCreateRequestDto.getDepositorName().length() > 20) { + throw new IllegalArgumentException("Depositor name is too long"); } } private String generateOrderSignature(Long storeId, Long tableId, List items) { diff --git a/common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 5fb5aecb..a0e33556 100644 --- a/common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -18,6 +18,7 @@ public enum ErrorMessage { ORDER_PARAMETER_EMPTY("주문 생성 시 파라미터 정보가 없습니다.", "order001"), ORDER_ITEMS_EMPTY("주문 항목이 없습니다.", "order002"), DUPLICATE_ORDER("동일한 주문이 접수되었습니다.", "order003"), + DEPOSITOR_NAME_TOO_LONG("주문자명은 10자 이내 글자열입니다.", "order004"), //reservation NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"), diff --git a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java index 4228c290..06909f2c 100644 --- a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java +++ b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java @@ -46,7 +46,7 @@ public class UserOrder extends BaseTimeEntity { private List orderItems = new ArrayList<>(); private String sessionId; - + @Column(length = 10) // 예약자 이름 길이 제한 private String depositorName; } diff --git a/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java b/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java new file mode 100644 index 00000000..e6a1e9aa --- /dev/null +++ b/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java @@ -0,0 +1,9 @@ +package com.nowait.order.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class DepositorNameTooLongException extends RuntimeException { + public DepositorNameTooLongException() { + super(ErrorMessage.DUPLICATE_ORDER.getMessage()); + } +} From 2f0b5c2c6004cd4331d89adb5a7b25215ec922f6 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 1 Jul 2025 18:07:40 +0900 Subject: [PATCH 076/685] =?UTF-8?q?feat(APIAdmin,Order):=20order=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order에 입금자명 추가 - reservation exception 디렉토리 위치 변경 --- .../nowait/order/exception/DepositorNameTooLongException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java b/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java index e6a1e9aa..b5da206e 100644 --- a/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java +++ b/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java @@ -4,6 +4,6 @@ public class DepositorNameTooLongException extends RuntimeException { public DepositorNameTooLongException() { - super(ErrorMessage.DUPLICATE_ORDER.getMessage()); + super(ErrorMessage.DEPOSITOR_NAME_TOO_LONG.getMessage()); } } From 3f8cdea3c0b0c79ab61ca06c309cea368b0a5577 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 1 Jul 2025 18:46:14 +0900 Subject: [PATCH 077/685] =?UTF-8?q?fix(Order):=20order=EC=B9=BC=EB=9F=BC?= =?UTF-8?q?=20=EC=A0=9C=EC=95=BD=EC=A1=B0=EA=B1=B4=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/order/dto/OrderCreateRequestDto.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java index 1bdd2528..8823d0e4 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java @@ -12,9 +12,9 @@ @AllArgsConstructor @Builder public class OrderCreateRequestDto { - private final String depositorName; // 예약자 이름 @NotBlank(message = "주문자 이름은 필수입니다") - @Size(max = 100, message = "주문자 이름은 10자 이하여야 합니다") + @Size(max = 10, message = "주문자 이름은 10자 이하여야 합니다") + private final String depositorName; // 예약자 이름 private final List items; // 장바구니 항목 리스트 } From 05735acf79427f74a100989c4ec1fa9354a1f014 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 1 Jul 2025 23:16:52 +0900 Subject: [PATCH 078/685] =?UTF-8?q?feat:=20swagger.config=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application-config/build.gradle | 2 ++ .../nowait/config/config/SwaggerConfig.java | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java diff --git a/application-config/build.gradle b/application-config/build.gradle index a6b89a70..788a7e3f 100644 --- a/application-config/build.gradle +++ b/application-config/build.gradle @@ -11,4 +11,6 @@ repositories { dependencies { api 'org.springframework.boot:spring-boot-starter-web' + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' } diff --git a/application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java b/application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java new file mode 100644 index 00000000..7c6454c0 --- /dev/null +++ b/application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java @@ -0,0 +1,30 @@ +package com.nowait.config.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .addSecurityItem(new SecurityRequirement().addList("JWT")) + .components(new Components() + .addSecuritySchemes("JWT", new SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER))) + .info(new Info().title("NOWAIT API") + .description("NOWAIT API Specification") + .version("v0.0.1")); + } +} From c4efe359a6980c20e11b3eae40672d5841323541 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Wed, 2 Jul 2025 20:47:43 +0900 Subject: [PATCH 079/685] =?UTF-8?q?feat(Order):=20order=20=ED=8B=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/controller/OrderController.java | 5 +++++ .../applicationuser/order/controller/OrderController.java | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 2adb243b..65c815c6 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.order.controller; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -11,5 +12,9 @@ @RequestMapping("admin//orders") @RequiredArgsConstructor public class OrderController { + @GetMapping + public void getOrders() { + //TODO + } } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 87cb3fe0..1c5f6df1 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -57,7 +57,6 @@ public ResponseEntity getOrderItems( @PathVariable Long tableId, HttpSession session ) { - // 세션ID 추출 (Spring이 세션 자동 관리) String sessionId = session.getId(); List orderItems = orderService.getOrderItems(storeId, tableId, sessionId); From a186e7835eb795ecd5cb2b0d0af6f23296a1755d Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Wed, 2 Jul 2025 21:37:49 +0900 Subject: [PATCH 080/685] =?UTF-8?q?feat(Order):=20order=20=ED=8B=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/controller/OrderController.java | 2 ++ .../applicationuser/order/controller/OrderController.java | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 65c815c6..c1a25862 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -15,6 +15,8 @@ public class OrderController { @GetMapping public void getOrders() { //TODO + } + } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 1c5f6df1..43be357d 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -58,7 +58,6 @@ public ResponseEntity getOrderItems( HttpSession session ) { String sessionId = session.getId(); - List orderItems = orderService.getOrderItems(storeId, tableId, sessionId); return ResponseEntity. status(HttpStatus.OK) From 2399095e7e5ffd0589fa7111a28b35aee43fac91 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Wed, 2 Jul 2025 22:10:22 +0900 Subject: [PATCH 081/685] =?UTF-8?q?refactor(Oauth):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=9B=84=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89?= =?UTF-8?q?=ED=8A=B8=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth2/OAuth2LoginSuccessHandler.java | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java b/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java index ea397a72..68206c61 100644 --- a/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/external-oauth/src/main/java/com/nowait/externaloauth/oauth2/OAuth2LoginSuccessHandler.java @@ -19,6 +19,7 @@ import com.nowait.externaloauth.jwt.JwtUtil; import com.nowait.user.entity.User; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -40,42 +41,32 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - // 1. CustomOAuth2UserService에서 설정한 OAuth2User 정보 가져오기 - CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); - + CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal(); User user = customUserDetails.getUser(); Long userId = customUserDetails.getUserId(); - String email = customUserDetails.getName(); - - Collection authorities = authentication.getAuthorities(); - Iterator iterator = authorities.iterator(); - GrantedAuthority auth = iterator.next(); - - String role = auth.getAuthority(); + String role = authentication.getAuthorities().iterator().next().getAuthority(); - log.info("user, userId, email, role :: {} {} {} {}", user, userId, email, role); + // JWT 발급 + String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 30 * 60 * 1000L); // 30분 + String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, 30L * 24 * 60 * 60 * 1000L); // 30일 - // 2. 1)의 사용자 정보를 담아, accessToken과 refreshToken 발행 - String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 30 * 60 * 1000L); // 유효기간 30분 - String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, - 30 * 24 * 60 * 60 * 1000L); // 유효기간 30일 - - // 3. refreshToken을 DB에 저장 + // 1. refreshToken을 DB에 저장 Token refreshTokenEntity = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); tokenRepository.save(refreshTokenEntity); - // 4. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. - response.setContentType("application/json"); - response.setCharacterEncoding("utf-8"); + // 2. refreshToken을 HttpOnly 쿠키로 설정 + Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken); + refreshTokenCookie.setHttpOnly(true); // JS 접근 불가 + refreshTokenCookie.setSecure(false); // 운영환경 https라면 true로 변경 필요 + refreshTokenCookie.setPath("/"); + refreshTokenCookie.setMaxAge(30 * 24 * 60 * 60); // 30일 + response.addCookie(refreshTokenCookie); + response.addHeader("Set-Cookie", response.getHeader("Set-Cookie") + "; SameSite=Lax"); + - ObjectMapper objectMapper = new ObjectMapper(); // 객체 -> json 문자열로 변환 - String body = objectMapper.writeValueAsString( - Map.of( - "accessToken", accessToken, - "refreshToken", refreshToken - ) - ); - response.getWriter().write(body); + // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) + String targetUrl = "http://localhost:5173/login/success?accessToken=" + accessToken; + response.sendRedirect(targetUrl); } } From 6e01b5903dfd26acb8ffb00d6dbe9ba84807b9c7 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 3 Jul 2025 17:31:41 +0900 Subject: [PATCH 082/685] =?UTF-8?q?feat(user):=20=EC=8A=88=ED=8D=BC?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SUPER_ADMIN 추가 - git 추적에 빠졌던 파일 git 추적하도록 추가 --- ...4\353\223\234-\354\235\264\354\212\210.md" | 14 ++ .../example/apiuser/ApiUserApplication.java | 39 ++++ .../controller/BookmarkController.java | 67 +++++++ .../bookmark/dto/BookmarkCreateResponse.java | 24 +++ .../bookmark/dto/BookmarkGetResponse.java | 24 +++ .../bookmark/service/BookmarkService.java | 78 ++++++++ .../exception/GlobalExceptionHandler.java | 170 ++++++++++++++++++ .../menu/controller/MenuController.java | 32 ++++ .../menu/dto/MenuImageUploadResponse.java | 20 +++ .../example/apiuser/menu/dto/MenuReadDto.java | 32 ++++ .../apiuser/menu/dto/MenuReadResponse.java | 21 +++ .../apiuser/menu/service/MenuService.java | 42 +++++ .../order/controller/OrderController.java | 70 ++++++++ .../apiuser/order/dto/CartItemDto.java | 11 ++ .../order/dto/OrderCreateRequestDto.java | 15 ++ .../order/dto/OrderCreateResponseDto.java | 28 +++ .../dto/OrderItemListGetResponseDto.java | 29 +++ .../order/dto/OrderItemResponseDTO.java | 16 ++ .../apiuser/order/service/OrderService.java | 128 +++++++++++++ .../controller/ReservationController.java | 47 +++++ .../dto/ReservationCreateRequestDto.java | 10 ++ .../dto/ReservationCreateResponseDto.java | 17 ++ .../ReservationNotFoundException.java | 9 + .../service/ReservationService.java | 58 ++++++ .../store/controller/StoreController.java | 56 ++++++ .../store/dto/StoreImageUploadResponse.java | 22 +++ .../apiuser/store/dto/StoreReadDto.java | 39 ++++ .../apiuser/store/dto/StoreReadResponse.java | 23 +++ .../apiuser/store/service/StoreService.java | 16 ++ .../store/service/StoreServiceImpl.java | 73 ++++++++ .../token/controller/TokenController.java | 59 ++++++ .../token/dto/AuthenticationResponse.java | 18 ++ .../token/dto/RefreshTokenRequest.java | 12 ++ .../apiuser/token/service/TokenService.java | 66 +++++++ .../user/serivce/UserService.java | 8 +- .../applicationconfig/config/AsyncConfig.java | 21 +++ .../applicationconfig/config/CorsConfig.java | 28 +++ .../token/controller/TokenController.java | 7 +- .../java/com/nowait/common/enums/Role.java | 3 +- .../com/example/domaintoken/entity/Token.java | 54 ++++++ .../exception/BusinessException.java | 16 ++ .../repository/TokenRepository.java | 11 ++ .../infrastorage/config/AwsS3Config.java | 33 ++++ .../example/infrastorage/s3/S3Service.java | 57 ++++++ .../com/nowait/auth/config/CorsConfig.java | 28 +++ .../nowait/auth/config/SecurityConfig.java | 93 ++++++++++ .../com/nowait/auth/dto/CustomOAuth2User.java | 60 +++++++ .../com/nowait/auth/dto/KaKaoResponse.java | 40 +++++ .../com/nowait/auth/dto/OAuth2Response.java | 19 ++ .../auth/jwt/JwtAuthorizationFilter.java | 93 ++++++++++ .../java/com/nowait/auth/jwt/JwtUtil.java | 76 ++++++++ .../auth/oauth2/CustomOAuth2UserService.java | 70 ++++++++ .../oauth2/OAuth2LoginSuccessHandler.java | 80 +++++++++ .../auth/service/CustomUserDetailService.java | 30 ++++ .../nowait/exception/BusinessException.java | 14 ++ .../com/nowait/exception/ErrorMessage.java | 22 +++ .../com/nowait/exception/ErrorResponse.java | 26 +++ .../exception/GlobalExceptionHandler.java | 108 +++++++++++ .../RefreshTokenNotFoundException.java | 9 + .../exception/ResourceNotFoundException.java | 14 ++ .../exception/TokenBadRequestException.java | 8 + .../exception/UnauthorizedException.java | 19 ++ 62 files changed, 2429 insertions(+), 3 deletions(-) create mode 100644 ".github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" create mode 100644 api-user/src/main/java/com/example/apiuser/ApiUserApplication.java create mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java create mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java create mode 100644 api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java create mode 100644 api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java create mode 100644 api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java create mode 100644 api-user/src/main/java/com/example/apiuser/order/service/OrderService.java create mode 100644 api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java create mode 100644 api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java create mode 100644 api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java create mode 100644 api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java create mode 100644 api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java create mode 100644 api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/store/service/StoreService.java create mode 100644 api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java create mode 100644 api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java create mode 100644 api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java create mode 100644 api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java create mode 100644 api-user/src/main/java/com/example/apiuser/token/service/TokenService.java create mode 100644 application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java create mode 100644 application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java create mode 100644 domain-token/src/main/java/com/example/domaintoken/entity/Token.java create mode 100644 domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java create mode 100644 domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java create mode 100644 infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java create mode 100644 infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java create mode 100644 security-front/src/main/java/com/nowait/auth/config/CorsConfig.java create mode 100644 security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java create mode 100644 security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java create mode 100644 security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java create mode 100644 security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java create mode 100644 security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java create mode 100644 security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java create mode 100644 security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java create mode 100644 security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java create mode 100644 security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java create mode 100644 security-front/src/main/java/com/nowait/exception/BusinessException.java create mode 100644 security-front/src/main/java/com/nowait/exception/ErrorMessage.java create mode 100644 security-front/src/main/java/com/nowait/exception/ErrorResponse.java create mode 100644 security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java create mode 100644 security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java create mode 100644 security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java create mode 100644 security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java create mode 100644 security-front/src/main/java/com/nowait/exception/UnauthorizedException.java diff --git "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" new file mode 100644 index 00000000..f728915f --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" @@ -0,0 +1,14 @@ +--- +name: 백엔드 이슈 +about: 백엔드와 관련된 이슈 +title: "[백엔드] " +labels: Backend +assignees: '' + +--- + +# 이슈 내용 + + +# 작업 목록 +- [ ] diff --git a/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java b/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java new file mode 100644 index 00000000..fcaf441e --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java @@ -0,0 +1,39 @@ +package com.example.apiuser; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@EnableJpaAuditing +@SpringBootApplication(scanBasePackages = { + "com.example.apiuser", + "com.nowait.auth" +}) +@EntityScan(basePackages = { + "com.example.menu.entity", // domain-menu + "com.example.domainstore.entity", // domain-store + "com.example.domaintoken.entity", + "com.nowaiting.user.entity", + "com.nowait.domainbookmark.entity", + "com.nowait.domainreservation.entity", + "com.nowait.domainorder.entity", + "com.nowait.domainorder.entity" +}) +@EnableJpaRepositories(basePackages = { + "com.example.menu.repository", + "com.nowaiting.user.repository", + "com.example.domainstore.repository", + "com.example.domaintoken.repository", + "com.nowait.domainbookmark.repository", + "com.nowait.domainorder.repository", + "com.nowait.domainreservation.repository" +}) +public class ApiUserApplication { + + public static void main(String[] args) { + SpringApplication.run(ApiUserApplication.class, args); + } + +} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java b/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java new file mode 100644 index 00000000..32c4132a --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java @@ -0,0 +1,67 @@ +package com.example.apiuser.bookmark.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.apiuser.bookmark.dto.BookmarkCreateResponse; +import com.example.apiuser.bookmark.service.BookmarkService; +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowaiting.common.api.ApiUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +@Tag(name = "Bookmark API", description = "북마크 API") +@RestController +@RequestMapping("/bookmarks") +@RequiredArgsConstructor +public class BookmarkController { + private final BookmarkService bookmarkService; + + @PostMapping("/{storeId}") + @Operation(summary = "북마크 생성", description = "특정 주점에 대한 북마크 생성") + @ApiResponse(responseCode = "201", description = "북마크 생성") + public ResponseEntity createBookmark(@PathVariable Long storeId,@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + BookmarkCreateResponse response = bookmarkService.createBookmark(storeId,customOAuth2User); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success( + response + ) + ); + } + @GetMapping + @Operation(summary = "북마크 조회", description = "내가 북마크한 주점 조회") + @ApiResponse(responseCode = "200", description = "북마크 조회") + public ResponseEntity getAllBookmarks(@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + return ResponseEntity + .ok() + .body( + ApiUtils.success( + bookmarkService.getBookmarks(customOAuth2User) + ) + ); + } + @DeleteMapping("/{bookmarkId}") + @Operation(summary = "북마크 삭제", description = "특정 주점에 대한 북마크 삭제") + @ApiResponse(responseCode = "200", description = "북마크 삭제") + public ResponseEntity deleteBookmark(@PathVariable Long bookmarkId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + return ResponseEntity + .ok() + .body( + ApiUtils.success( + bookmarkService.deleteBookmark(bookmarkId,customOAuth2User) + ) + ); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java b/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java new file mode 100644 index 00000000..5e4a8f65 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java @@ -0,0 +1,24 @@ +package com.example.apiuser.bookmark.dto; + +import com.nowait.domainbookmark.entity.Bookmark; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class BookmarkCreateResponse { + private Long bookmarkId; + private Long userId; + private Long storeId; + + public static BookmarkCreateResponse fromEntity(Bookmark bookmark) { + return BookmarkCreateResponse.builder() + .bookmarkId(bookmark.getId()) + .userId(bookmark.getUser().getId()) + .storeId(bookmark.getStore().getStoreId()) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java b/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java new file mode 100644 index 00000000..6f05db8e --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java @@ -0,0 +1,24 @@ +package com.example.apiuser.bookmark.dto; + +import com.nowait.domainbookmark.entity.Bookmark; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class BookmarkGetResponse { + private Long bookmarkId; + private Long userId; + private Long storeId; + + public static BookmarkGetResponse fromEntity(Bookmark bookmark) { + return BookmarkGetResponse.builder() + .bookmarkId(bookmark.getId()) + .userId(bookmark.getUser().getId()) + .storeId(bookmark.getStore().getStoreId()) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java b/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java new file mode 100644 index 00000000..91ce0ff8 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java @@ -0,0 +1,78 @@ +package com.example.apiuser.bookmark.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.apiuser.bookmark.dto.BookmarkCreateResponse; +import com.example.apiuser.bookmark.dto.BookmarkGetResponse; +import com.example.domainstore.entity.Store; +import com.example.domainstore.repository.StoreRepository; +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowait.domainbookmark.entity.Bookmark; +import com.nowait.domainbookmark.repository.BookmarkRepository; +import com.nowaiting.user.entity.User; +import com.nowaiting.user.repository.UserRepository; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BookmarkService { + private final BookmarkRepository bookmarkRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; + @Transactional + public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User customOAuth2User) { + parameterValidation(storeId, customOAuth2User); + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + User user = userRepository.findById(customOAuth2User.getUserId()) + .orElseThrow(() -> new EntityNotFoundException("User not found")); + + if (bookmarkRepository.existsByUserAndStore(user, store)) { + throw new IllegalArgumentException("already bookmarked"); + } + + Bookmark bookmark = Bookmark.builder() + .store(store) + .user(user) + .build(); + + return BookmarkCreateResponse.fromEntity(bookmarkRepository.save(bookmark)); + } + + @Transactional(readOnly = true) + public List getBookmarks(CustomOAuth2User customOAuth2User) { + User user = userRepository.findById(customOAuth2User.getUserId()) + .orElseThrow(() -> new EntityNotFoundException("User not found")); + return bookmarkRepository.findAllByUser(user) + .stream() + .map(BookmarkGetResponse::fromEntity) + .toList(); + } + + @Transactional + public String deleteBookmark(Long bookmarkId, CustomOAuth2User customOAuth2User) { + parameterValidation(bookmarkId, customOAuth2User); + Bookmark bookmark = bookmarkRepository.findById(bookmarkId) + .orElseThrow(() -> new EntityNotFoundException(bookmarkId + " bookmark not found.")); + if (bookmark.getUser().getId() != customOAuth2User.getUserId()) { + throw new IllegalArgumentException("you can only delete your own bookmark"); + } + bookmarkRepository.delete(bookmark); + return "Bookmark ID " + bookmarkId + " deleted."; + } + + private static void parameterValidation(Long storeId, CustomOAuth2User customOAuth2User) { + // 파라미터 유효성 검사 + if (storeId == null || storeId < 0) { + throw new IllegalArgumentException("storeId must be a positive number"); + } + if (customOAuth2User == null || customOAuth2User.getUserId() == null) { + throw new IllegalArgumentException("UserInfo is required"); + } + } +} diff --git a/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java b/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..fedb029f --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java @@ -0,0 +1,170 @@ +package com.example.apiuser.exception; + +import static com.nowaiting.common.exception.ErrorMessage.*; +import static org.springframework.http.HttpStatus.*; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingRequestValueException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MultipartException; + +import com.example.apiuser.reservation.exception.ReservationNotFoundException; +import com.nowait.domainbookmark.exception.BookmarkOwnerMismatchException; +import com.nowait.domainbookmark.exception.DuplicateBookmarkException; +import com.nowait.domainorder.exception.DuplicateOrderException; +import com.nowait.domainorder.exception.OrderItemsEmptyException; +import com.nowait.domainorder.exception.OrderParameterEmptyException; +import com.nowait.exception.BusinessException; +import com.nowait.exception.ResourceNotFoundException; +import com.nowait.exception.UnauthorizedException; +import com.nowaiting.common.exception.ErrorMessage; +import com.nowaiting.common.exception.ErrorResponse; +import com.nowaiting.user.exception.UserNotFoundException; + +import io.swagger.v3.oas.annotations.Hidden; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Hidden +@RestControllerAdvice +public class GlobalExceptionHandler { + + // OAUTH 인증 실패 에러처리 메서드 + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(OAuth2AuthenticationException.class) + public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e) { + log.error("handleOAuth2AuthenticationException", e); + + return new ErrorResponse("OAuth 인증 실패 : " + e.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(BusinessException.class) + public ErrorResponse handleBusinessException(BusinessException e) { + log.error("handleBusinessException", e); + return new ErrorResponse(e.getMessage(), e.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error("handleMethodArgumentNotValidException", e); + Map errors = getErrors(e); + return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(HttpMessageNotReadableException.class) + public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.error("handleHttpMessageNotReadableException", e); + return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(IllegalArgumentException.class) + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { + log.error("handleIllegalArgumentException", e); + return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(MissingRequestValueException.class) + public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { + log.error("handleMissingRequestValueExceptionException", e); + return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = UNAUTHORIZED) + @ExceptionHandler(UnauthorizedException.class) + public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { + log.error("handleUnauthorizedExceptionException", e); + return new ErrorResponse(e.getMessage(), e.getCode()); + } + + @ResponseStatus(value = NOT_FOUND) + @ExceptionHandler(ResourceNotFoundException.class) + public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { + log.error("handleResourceNotFoundExceptionException", e); + return new ErrorResponse(e.getMessage(), e.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(MultipartException.class) + public ErrorResponse handleMultipartException(MultipartException e) { + log.error("handleMultipartException", e); + return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(DuplicateBookmarkException.class) + public ErrorResponse handleDuplicateBookmarkException(DuplicateBookmarkException e) { + log.error("handleDuplicateBookmarkException", e); + return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_BOOKMARK.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(BookmarkOwnerMismatchException.class) + public ErrorResponse bookmarkOwnerMismatchException(BookmarkOwnerMismatchException e) { + log.error("bookmarkOwnerMismatchException", e); + return new ErrorResponse(e.getMessage(), NOT_OWN_BOOKMARK.getCode()); + } + + @ResponseStatus(value = NOT_FOUND) + @ExceptionHandler(UserNotFoundException.class) + public ErrorResponse userNotFoundException(UserNotFoundException e) { + log.error("userNotFoundException", e); + return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(OrderParameterEmptyException.class) + public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e) { + log.error("orderParameterEmptyException", e); + return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(OrderItemsEmptyException.class) + public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e) { + log.error("orderItemsEmptyException", e); + return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(DuplicateOrderException.class) + public ErrorResponse duplicateOrderException(DuplicateOrderException e) { + log.error("duplicateOrderException", e); + return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); + } + + @ResponseStatus(value = NOT_FOUND) + @ExceptionHandler(ReservationNotFoundException.class) + public ErrorResponse reservationNotFoundException(ReservationNotFoundException e) { + log.error("reservationNotFoundException", e); + return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); + } + + + private static Map getErrors(MethodArgumentNotValidException e) { + return e.getBindingResult() + .getAllErrors() + .stream() + .filter(ObjectError.class::isInstance) + .collect(Collectors.toMap( + error -> error instanceof FieldError ? ((FieldError)error).getField() : error.getObjectName(), + ObjectError::getDefaultMessage, + (msg1, msg2) -> msg1 + ";" + msg2 + )); + } + +} diff --git a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java b/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java new file mode 100644 index 00000000..d3fb6d02 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java @@ -0,0 +1,32 @@ +package com.example.apiuser.menu.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.apiuser.menu.service.MenuService; +import com.nowaiting.common.api.ApiUtils; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/v1/menus") +@RequiredArgsConstructor +public class MenuController { + + private final MenuService menuService; + + @GetMapping("/all-menus/stores/{storeId}") + public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + menuService.getMenusByStoreId(storeId) + ) + ); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java new file mode 100644 index 00000000..c32be356 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java @@ -0,0 +1,20 @@ +package com.example.apiuser.menu.dto; + +import com.example.menu.entity.MenuImage; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class MenuImageUploadResponse { + private final Long id; + private final String imageUrl; + + public static MenuImageUploadResponse fromEntity(MenuImage menuImage) { + return MenuImageUploadResponse.builder() + .id(menuImage.getId()) + .imageUrl(menuImage.getImageUrl()) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java new file mode 100644 index 00000000..98dda1f2 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java @@ -0,0 +1,32 @@ +package com.example.apiuser.menu.dto; + +import java.util.List; + +import com.example.menu.entity.Menu; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class MenuReadDto { + private Long menuId; + private Long storeId; + private String name; + private String description; + private Integer price; + private List images; + + public static MenuReadDto fromEntity(Menu menu, List images) { + return MenuReadDto.builder() + .menuId(menu.getId()) + .storeId(menu.getStoreId()) + .name(menu.getName()) + .description(menu.getDescription()) + .price(menu.getPrice()) + .images(images) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java new file mode 100644 index 00000000..755b243f --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java @@ -0,0 +1,21 @@ +package com.example.apiuser.menu.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class MenuReadResponse { + + private List menuReadDto; + + public static MenuReadResponse of(List menuReadDto) { + return MenuReadResponse.builder() + .menuReadDto(menuReadDto) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java new file mode 100644 index 00000000..64b9e0b9 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java @@ -0,0 +1,42 @@ +package com.example.apiuser.menu.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.apiuser.menu.dto.MenuImageUploadResponse; +import com.example.apiuser.menu.dto.MenuReadDto; +import com.example.apiuser.menu.dto.MenuReadResponse; +import com.example.menu.entity.Menu; +import com.example.menu.entity.MenuImage; +import com.example.menu.repository.MenuImageRepository; +import com.example.menu.repository.MenuRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class MenuService { + + private final MenuRepository menuRepository; + private final MenuImageRepository menuImageRepository; + + + @Transactional(readOnly = true) + public MenuReadResponse getMenusByStoreId(Long storeId) { + List menus = menuRepository.findAllByStoreId(storeId); + + List menuReadResponse = menus.stream() + .map(menu -> { + List images = menuImageRepository.findByMenu(menu); + List imageDto = images.stream() + .map(MenuImageUploadResponse::fromEntity) + .toList(); + return MenuReadDto.fromEntity(menu, imageDto); + }) + .toList(); + + return MenuReadResponse.of(menuReadResponse); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java b/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java new file mode 100644 index 00000000..65238120 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java @@ -0,0 +1,70 @@ +package com.example.apiuser.order.controller; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.apiuser.order.dto.OrderCreateRequestDto; +import com.example.apiuser.order.dto.OrderCreateResponseDto; +import com.example.apiuser.order.dto.OrderItemListGetResponseDto; +import com.example.apiuser.order.service.OrderService; +import com.nowaiting.common.api.ApiUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@Tag(name = "Order API", description = "주문 API") +@RestController +@RequestMapping("/orders") +@RequiredArgsConstructor +public class OrderController { + private final OrderService orderService; + + @PostMapping("/create/{storeId}/{tableId}") + @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") + @ApiResponse(responseCode = "201", description = "주문 생성") + public ResponseEntity createOrder( + @PathVariable Long storeId, + @PathVariable Long tableId, + @RequestBody @Valid OrderCreateRequestDto orderCreateRequestDto, + HttpSession session + ) { + String sessionId = session.getId(); + OrderCreateResponseDto response = orderService.createOrder(storeId,tableId,orderCreateRequestDto,sessionId); + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success(response) + ); + } + + @GetMapping("/items/{storeId}/{tableId}") + @Operation(summary = "테이블별 주문 아이템 조회", description = "비로그인(세션) 기준으로 테이블의 내 주문 목록만 조회") + @ApiResponse(responseCode = "200", description = "주문 조회") + public ResponseEntity getOrderItems( + @PathVariable Long storeId, + @PathVariable Long tableId, + HttpSession session + ) { + // 세션ID 추출 (Spring이 세션 자동 관리) + String sessionId = session.getId(); + + List orderItems = orderService.getOrderItems(storeId, tableId, sessionId); + return ResponseEntity. + status(HttpStatus.OK) + .body( + ApiUtils.success(orderItems) + ); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java new file mode 100644 index 00000000..1b8fc52b --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java @@ -0,0 +1,11 @@ +package com.example.apiuser.order.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CartItemDto { + private final Long menuId; // 메뉴 ID + private final int quantity; // 수량 +} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java new file mode 100644 index 00000000..056d44a6 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java @@ -0,0 +1,15 @@ +package com.example.apiuser.order.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class OrderCreateRequestDto { + private final List items; // 장바구니 항목 리스트 + +} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java new file mode 100644 index 00000000..1293ac0f --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java @@ -0,0 +1,28 @@ +package com.example.apiuser.order.dto; + +import java.util.List; + +import com.nowait.domainorder.entity.UserOrder; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class OrderCreateResponseDto { + private Long orderId; // 주문 ID // 주문 상태 (예: "주문완료", "배송중" 등) + private Long storeId; // 상점 ID + private String storeName; // 상점 이름 + private List orderItems; // 주문 항목 목록 + + public static OrderCreateResponseDto fromEntity(UserOrder order) { + return OrderCreateResponseDto.builder() + .orderId(order.getId()) + .storeId(order.getStore().getStoreId()) + .storeName(order.getStore().getName()) + .orderItems(List.of()) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java new file mode 100644 index 00000000..02e24e3f --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java @@ -0,0 +1,29 @@ +package com.example.apiuser.order.dto; + +import com.nowait.domainorder.entity.OrderItem; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OrderItemListGetResponseDto { + private Long orderId; + private String menuName; + private Integer quantity; + private Integer price; + + public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem) { + return OrderItemListGetResponseDto.builder() + .orderId(orderItem.getUserOrder().getId()) + .menuName(orderItem.getMenu().getName()) + .quantity(orderItem.getQuantity()) + .price(orderItem.getMenu().getPrice()) + .build(); + + } +} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java new file mode 100644 index 00000000..12f04514 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java @@ -0,0 +1,16 @@ +package com.example.apiuser.order.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class OrderItemResponseDTO { + private Long menuId; // 메뉴 ID + private String menuName; // 메뉴 이름 + private int quantity; // 수량 +} diff --git a/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java b/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java new file mode 100644 index 00000000..1644ba16 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java @@ -0,0 +1,128 @@ +package com.example.apiuser.order.service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.DigestUtils; + +import com.example.apiuser.order.dto.CartItemDto; +import com.example.apiuser.order.dto.OrderCreateRequestDto; +import com.example.apiuser.order.dto.OrderCreateResponseDto; +import com.example.apiuser.order.dto.OrderItemListGetResponseDto; +import com.example.domainstore.entity.Store; +import com.example.domainstore.repository.StoreRepository; +import com.example.menu.entity.Menu; +import com.example.menu.repository.MenuRepository; +import com.nowait.domainorder.entity.OrderItem; +import com.nowait.domainorder.entity.UserOrder; +import com.nowait.domainorder.exception.DuplicateOrderException; +import com.nowait.domainorder.exception.OrderItemsEmptyException; +import com.nowait.domainorder.exception.OrderParameterEmptyException; +import com.nowait.domainorder.repository.OrderItemRepository; +import com.nowait.domainorder.repository.OrderRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OrderService { + private final OrderRepository orderRepository; + private final StoreRepository storeRepository; + private final MenuRepository menuRepository; + private final OrderItemRepository orderItemRepository; + @Transactional + public OrderCreateResponseDto createOrder(Long storeId, Long tableId, + OrderCreateRequestDto orderCreateRequestDto, String sessionId) { + parameterValidation(storeId, tableId, orderCreateRequestDto); + + // 💡 [중복 주문 방지] signature 생성 및 체크 + String signature = generateOrderSignature(storeId, tableId, orderCreateRequestDto.getItems()); + checkDuplicateOrderSignature(signature); + + // 1. Store 조회 + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("store not found")); + + // 2. UserOrder 생성 및 signature 저장 + UserOrder order = UserOrder.builder() + .tableId(tableId) + .store(store) + .signature(signature) // signature 저장 + .sessionId(sessionId) // sessionId 저장 + .build(); + UserOrder savedOrder = orderRepository.save(order); + + // 3. 메뉴 ID 리스트 수집 -> Map으로 캐싱 + List menuIds = orderCreateRequestDto.getItems().stream() + .map(CartItemDto::getMenuId) + .toList(); + + List menus = menuRepository.findAllById(menuIds); + Map menuMap = menus.stream() + .collect(Collectors.toMap(Menu::getId, Function.identity())); + + // 4. 각 장바구니 항목에 대해 OrderItem 생성 및 저장 + List orderItems = orderCreateRequestDto.getItems().stream() + .map(item -> { + Menu menu = Optional.ofNullable(menuMap.get(item.getMenuId())) + .orElseThrow(() -> new IllegalArgumentException("menu not found: " + item.getMenuId())); + return OrderItem.builder() + .userOrder(savedOrder) + .menu(menu) + .quantity(item.getQuantity()) + .build(); + }) + .collect(Collectors.toList()); + + + orderItemRepository.saveAll(orderItems); + + // 5. 응답 반환 + return OrderCreateResponseDto.fromEntity(savedOrder); + } + + @Transactional(readOnly = true) + public List getOrderItems(Long storeId, Long tableId, String sessionId) { + // 1. UserOrder 목록 조회 (storeId, tableId, sessionId 기준) + List userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId); + + // 2. OrderItem으로 변환 + return userOrders.stream() + .flatMap(order -> order.getOrderItems().stream()) + .map(OrderItemListGetResponseDto::fromEntity) + .toList(); + } + + + private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { + if (storeId == null || tableId == null || orderCreateRequestDto == null) { + throw new OrderParameterEmptyException(); + } + if (orderCreateRequestDto.getItems() == null || orderCreateRequestDto.getItems().isEmpty()) { + throw new OrderItemsEmptyException(); + } + } + private String generateOrderSignature(Long storeId, Long tableId, List items) { + String cartString = items.stream() + .sorted((a, b) -> a.getMenuId().compareTo(b.getMenuId())) // 메뉴 ID 기준 정렬 + .map(item -> item.getMenuId() + ":" + item.getQuantity()) + .collect(Collectors.joining(",")); + String raw = storeId + "-" + tableId + "-" + cartString; + return DigestUtils.md5DigestAsHex(raw.getBytes()); + } + + private void checkDuplicateOrderSignature(String signature) { + // 최근 2초 이내 동일 signature 주문이 있는지 검사 + LocalDateTime threshold = LocalDateTime.now().minusSeconds(2); + boolean exists = orderRepository.existsBySignatureAndCreatedAtAfter(signature, threshold); + if (exists) { + throw new DuplicateOrderException(); + } + } +} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java b/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java new file mode 100644 index 00000000..75416513 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java @@ -0,0 +1,47 @@ +package com.example.apiuser.reservation.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.apiuser.reservation.dto.ReservationCreateRequestDto; +import com.example.apiuser.reservation.dto.ReservationCreateResponseDto; +import com.example.apiuser.reservation.service.ReservationService; +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowaiting.common.api.ApiUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "Reservation API", description = "예약 API") +@RestController +@RequestMapping("/reservations") +@RequiredArgsConstructor +public class ReservationController { + + private final ReservationService reservationService; + + @PostMapping("/create/{storeId}") + @Operation(summary = "예약 생성", description = "특정 주점에 대한 예약하기 생성") + @ApiResponse(responseCode = "201", description = "예약 생성") + public ResponseEntity create( + @PathVariable Long storeId, + @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @RequestBody ReservationCreateRequestDto requestDto) { + ReservationCreateResponseDto response = reservationService.create(storeId, customOAuth2User, requestDto); + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success( + response + ) + ); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java b/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java new file mode 100644 index 00000000..03fbfb4f --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java @@ -0,0 +1,10 @@ +package com.example.apiuser.reservation.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ReservationCreateRequestDto { + private Integer partySize; +} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java b/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java new file mode 100644 index 00000000..fa581589 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java @@ -0,0 +1,17 @@ +package com.example.apiuser.reservation.dto; + +import java.time.LocalDateTime; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ReservationCreateResponseDto { + private Long id; + private Long storeId; + private Long userId; + private LocalDateTime requestedAt; + private String status; + private Integer partySize; +} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java b/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java new file mode 100644 index 00000000..37dc86eb --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java @@ -0,0 +1,9 @@ +package com.example.apiuser.reservation.exception; + +import com.nowaiting.common.exception.ErrorMessage; + +public class ReservationNotFoundException extends RuntimeException { + public ReservationNotFoundException() { + super(ErrorMessage.NOTFOUND_RESERVATION.getMessage()); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java b/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java new file mode 100644 index 00000000..a5394ec8 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java @@ -0,0 +1,58 @@ +package com.example.apiuser.reservation.service; + +import java.time.LocalDateTime; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.apiuser.reservation.dto.ReservationCreateRequestDto; +import com.example.apiuser.reservation.dto.ReservationCreateResponseDto; +import com.example.domainstore.entity.Store; +import com.example.domainstore.repository.StoreRepository; +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowait.domainreservation.entity.Reservation; +import com.nowait.domainreservation.repository.ReservationRepository; +import com.nowaiting.common.enums.ReservationStatus; +import com.nowaiting.user.entity.User; +import com.nowaiting.user.exception.UserNotFoundException; +import com.nowaiting.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ReservationService { + + private final ReservationRepository reservationRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; + + @Transactional + public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User, + ReservationCreateRequestDto requestDto) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 store")); + User user = userRepository.findById(customOAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + + Reservation reservation = Reservation.builder() + .store(store) + .user(user) + .requestedAt(LocalDateTime.now()) + .status(ReservationStatus.WAITING) + .partySize(requestDto.getPartySize()) + .build(); + + Reservation saved = reservationRepository.save(reservation); + + return ReservationCreateResponseDto.builder() + .id(saved.getId()) + .storeId(saved.getStore().getStoreId()) + .userId(saved.getUser().getId()) + .requestedAt(saved.getRequestedAt()) + .status(saved.getStatus().name()) + .partySize(saved.getPartySize()) + .build(); + } +} + diff --git a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java b/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java new file mode 100644 index 00000000..510b19d7 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java @@ -0,0 +1,56 @@ +package com.example.apiuser.store.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.example.apiuser.store.service.StoreService; +import com.nowaiting.common.api.ApiUtils; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("v1/stores") +@RequiredArgsConstructor +public class StoreController { + + private final StoreService storeService; + + + @GetMapping("/all-stores") + public ResponseEntity getAllStores() { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storeService.getAllStores() + ) + ); + } + + @GetMapping("/{storeId}") + public ResponseEntity getStoreById(@PathVariable Long storeId) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storeService.getStoreByStoreId(storeId) + ) + ); + } + + @GetMapping("/search") + public ResponseEntity searchStores(@RequestParam("name") String name) { + return ResponseEntity + .ok() + .body( + ApiUtils.success( + storeService.searchStoresByName(name) + ) + ); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java new file mode 100644 index 00000000..40cb984d --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java @@ -0,0 +1,22 @@ +package com.example.apiuser.store.dto; + +import com.example.domainstore.entity.StoreImage; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class StoreImageUploadResponse { + private final Long id; + private final String imageUrl; + private final String type; + + public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { + return StoreImageUploadResponse.builder() + .id(storeImage.getId()) + .imageUrl(storeImage.getImageUrl()) + .type(storeImage.getType()) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java new file mode 100644 index 00000000..d6cfab6e --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java @@ -0,0 +1,39 @@ +package com.example.apiuser.store.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.example.domainstore.entity.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreReadDto { + private Long storeId; + private Long departmentId; + private String name; + private String location; + private String description; + private List images; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + + public static StoreReadDto fromEntity(Store store, List images) { + return StoreReadDto.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .departmentId(store.getDepartmentId()) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .images(images) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java new file mode 100644 index 00000000..a580e562 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java @@ -0,0 +1,23 @@ +package com.example.apiuser.store.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreReadResponse { + + private List storeReadDtos; + private boolean hasNext; + + public static StoreReadResponse of(List storeReadDtos, boolean hasNext) { + return StoreReadResponse.builder() + .storeReadDtos(storeReadDtos) + .hasNext(hasNext) + .build(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java b/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java new file mode 100644 index 00000000..fd90de2a --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java @@ -0,0 +1,16 @@ +package com.example.apiuser.store.service; + +import java.util.List; + +import com.example.apiuser.store.dto.StoreReadDto; +import com.example.apiuser.store.dto.StoreReadResponse; + +public interface StoreService { + + StoreReadResponse getAllStores(); + + StoreReadDto getStoreByStoreId(Long storeId); + + List searchStoresByName(String name); + +} diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java b/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java new file mode 100644 index 00000000..d641f03e --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java @@ -0,0 +1,73 @@ +package com.example.apiuser.store.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.example.apiuser.store.dto.StoreImageUploadResponse; +import com.example.apiuser.store.dto.StoreReadDto; +import com.example.apiuser.store.dto.StoreReadResponse; +import com.example.domainstore.entity.Store; +import com.example.domainstore.entity.StoreImage; +import com.example.domainstore.repository.StoreImageRepository; +import com.example.domainstore.repository.StoreRepository; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class StoreServiceImpl implements StoreService { + + private final StoreRepository storeRepository; + private final StoreImageRepository storeImageRepository; + + + @Override + @Transactional(readOnly = true) + public StoreReadResponse getAllStores() { + List stores = storeRepository.findAllByDeletedFalse(); + + List storeRead = stores.stream() + .map(store -> { + List images = storeImageRepository.findByStore(store); + List imageDto = images.stream() + .map(StoreImageUploadResponse::fromEntity) + .toList(); + return StoreReadDto.fromEntity(store, imageDto); + }) + .toList(); + + boolean hasNext = false; + + return StoreReadResponse.of(storeRead, hasNext); + } + + @Override + @Transactional(readOnly = true) + public StoreReadDto getStoreByStoreId(Long storeId) { + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) + .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + + List images = storeImageRepository.findByStore(store); + List imageDto = images.stream() + .map(StoreImageUploadResponse::fromEntity) + .toList(); + + return StoreReadDto.fromEntity(store, imageDto); + } + + @Override + public List searchStoresByName(String name) { + List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(name); + return stores.stream() + .map(store -> { + List images = storeImageRepository.findByStore(store); + List imageDto = images.stream() + .map(StoreImageUploadResponse::fromEntity) + .toList(); + return StoreReadDto.fromEntity(store, imageDto); + }) + .toList(); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java b/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java new file mode 100644 index 00000000..67bafe53 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java @@ -0,0 +1,59 @@ +package com.example.apiuser.token.controller; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.apiuser.token.dto.AuthenticationResponse; +import com.example.apiuser.token.dto.RefreshTokenRequest; +import com.example.apiuser.token.service.TokenService; +import com.nowait.auth.jwt.JwtUtil; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/refresh-token") +@Slf4j +public class TokenController { + private final JwtUtil jwtUtil; + private final TokenService tokenService; + @Value("${jwt.access-token-expiration-ms}") + private long accessTokenExpiration; + @Value("${jwt.refresh-token-expiration-ms}") + private long refreshTokenExpiration; + @PostMapping + public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ + String refreshToken = request.getRefreshToken(); + + // 리프레시 토큰 검증 + Long userId = jwtUtil.getUserId(refreshToken); + String role = jwtUtil.getRole(refreshToken); + + long currentAccessTokenExpiration = accessTokenExpiration; + if (role.equals("SUPER_ADMIN")) { + currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일 + } + + // 리프레시 토큰 유효성 검증 + if (tokenService.validateToken(refreshToken, userId)){ + // 유효한 토큰이라면, 새로운 accessToken, refreshToken 생성 + String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, currentAccessTokenExpiration); + String newRefreshToken = jwtUtil.createRefreshToken("refreshToken", userId, refreshTokenExpiration); + + // DB에 새로운 refreshToken으로 교체 + tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); + + AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, refreshToken); + return ResponseEntity.ok().body(authenticationResponse); + + } + + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); + } +} diff --git a/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java b/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java new file mode 100644 index 00000000..71db8914 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java @@ -0,0 +1,18 @@ +package com.example.apiuser.token.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@RequiredArgsConstructor +@Getter +@ToString(exclude = {"accessToken", "refreshToken"}) // 로깅 시 토큰 노출 방지 +public class AuthenticationResponse { + @JsonProperty("access_token") + private final String accessToken; + + @JsonProperty("refresh_token") + private final String refreshToken; +} diff --git a/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java b/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java new file mode 100644 index 00000000..4ab8ae75 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java @@ -0,0 +1,12 @@ +package com.example.apiuser.token.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class RefreshTokenRequest { + @NotBlank(message = "Refresh token은 필수입니다.") + private String refreshToken; +} diff --git a/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java b/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java new file mode 100644 index 00000000..56845238 --- /dev/null +++ b/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java @@ -0,0 +1,66 @@ +package com.example.apiuser.token.service; + +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.domaintoken.entity.Token; +import com.example.domaintoken.repository.TokenRepository; +import com.nowait.auth.jwt.JwtUtil; +import com.nowait.exception.RefreshTokenNotFoundException; +import com.nowait.exception.TokenBadRequestException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class TokenService { + private final TokenRepository tokenRepository; + private final JwtUtil jwtUtil; + + @Transactional + public Boolean validateToken(String token, Long userId){ + // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. + Optional savedToken = tokenRepository.findByUserId(userId); + + // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 + if (savedToken.isEmpty()){ + log.info("여기에 걸렸니 ? -- 1 "); + return false; + } + + // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 + if (!savedToken.get().getRefreshToken().equals(token)){ + log.info("여기에 걸렸니 ? -- 2 "); + return false; + } + + // 리프레시 토큰의 만료여부 확인 + if(jwtUtil.isExpired(token)){ + log.info("여기에 걸렸니 ? -- 3 "); + return false; // 만료된 토큰은 유효하지 않음 + } + + log.info("여기에 걸렸니 ? -- 4 "); + return true; // 모든 조건 만족 시, 유효한 토큰 + } + + @Transactional + public void updateRefreshToken(Long userId, String oldRefreshToken, String newRefreshToken){ + Token token = tokenRepository.findByUserId(userId) + .orElseThrow(RefreshTokenNotFoundException::new); // 404 + + if (!token.getRefreshToken().equals(oldRefreshToken)){ + throw new TokenBadRequestException(); // 400 + } + + // 기존 토큰 삭제 및 새 토큰 저장 + tokenRepository.delete(token); + Token newToken = Token.toEntity(token.getUser(), newRefreshToken, LocalDateTime.now().plusDays(30)); + tokenRepository.save(newToken); + } +} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java b/application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java index 9a11c497..7a797a9b 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java @@ -60,7 +60,13 @@ public ManagerLoginResponseDto login(ManagerLoginRequestDto managerLoginRequestD ); MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); User user = userRepository.getReferenceById(memberDetails.getId()); - String accessToken = jwtUtil.createAccessToken("accessToken", user.getId(), String.valueOf(user.getRole()), accessTokenExpiration); + + long currentAccessTokenExpiration = accessTokenExpiration; + if (user.getRole() == com.nowait.common.enums.Role.SUPER_ADMIN) { + currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일 + } + + String accessToken = jwtUtil.createAccessToken("accessToken", user.getId(), String.valueOf(user.getRole()), currentAccessTokenExpiration); return ManagerLoginResponseDto.fromEntity(user,accessToken); } } diff --git a/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java b/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java new file mode 100644 index 00000000..9c839fbb --- /dev/null +++ b/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java @@ -0,0 +1,21 @@ +package com.example.applicationconfig.config; + +import java.util.concurrent.Executor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class AsyncConfig { + @Bean(name = "s3UploadExecutor") + public Executor s3UploadExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("S3Upload-"); + executor.initialize(); + return executor; + } +} diff --git a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java b/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java new file mode 100644 index 00000000..d8615194 --- /dev/null +++ b/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java @@ -0,0 +1,28 @@ +package com.example.applicationconfig.config; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +public class CorsConfig { + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + + config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 + config.setAllowedOrigins(List.of("http://localhost:3000")); // 허용할 출처 설정 + config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 + config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 + config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 + // config.setAllowCredentials(true); // 쿠키 포함 허용 + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 + return source; + } +} diff --git a/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index c9298d04..009fafe4 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/application-user/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -40,10 +40,15 @@ public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ Long userId = jwtUtil.getUserId(refreshToken); String role = jwtUtil.getRole(refreshToken); + long currentAccessTokenExpiration = accessTokenExpiration; + if (role.equals("SUPER_ADMIN")) { + currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일 + } + // 리프레시 토큰 유효성 검증 if (tokenService.validateToken(refreshToken, userId)){ // 유효한 토큰이라면, 새로운 accessToken, refreshToken 생성 - String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, accessTokenExpiration); + String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, currentAccessTokenExpiration); String newRefreshToken = jwtUtil.createRefreshToken("refreshToken", userId, refreshTokenExpiration); // DB에 새로운 refreshToken으로 교체 diff --git a/common/src/main/java/com/nowait/common/enums/Role.java b/common/src/main/java/com/nowait/common/enums/Role.java index 5aed5a91..ddbd6911 100644 --- a/common/src/main/java/com/nowait/common/enums/Role.java +++ b/common/src/main/java/com/nowait/common/enums/Role.java @@ -2,7 +2,8 @@ public enum Role { USER("USER"), - MANAGER("MANAGER"); + MANAGER("MANAGER"), + SUPER_ADMIN("SUPER_ADMIN"); private final String name; diff --git a/domain-token/src/main/java/com/example/domaintoken/entity/Token.java b/domain-token/src/main/java/com/example/domaintoken/entity/Token.java new file mode 100644 index 00000000..3bd47167 --- /dev/null +++ b/domain-token/src/main/java/com/example/domaintoken/entity/Token.java @@ -0,0 +1,54 @@ +package com.example.domaintoken.entity; + +import java.time.LocalDateTime; + +import com.nowait.user.entity.User; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "token") +@Getter +public class Token { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long tokenId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column + private String refreshToken; + + private LocalDateTime expiredDate; + + @Builder + public Token(User user, String refreshToken, LocalDateTime expiredDate) { + this.user = user; + this.refreshToken = refreshToken; + this.expiredDate = expiredDate; + } + + // static method로 객체를 생성 - 생성 의도 파악 쉬웁 + public static Token toEntity(User user, String refreshToken, LocalDateTime expiredDate){ + return Token.builder() + .user(user) + .refreshToken(refreshToken) + .expiredDate(expiredDate) + .build(); + } + +} diff --git a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java b/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java new file mode 100644 index 00000000..05dc5c67 --- /dev/null +++ b/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java @@ -0,0 +1,16 @@ +package com.example.domaintoken.exception; + +import com.nowaiting.common.exception.ErrorMessage; + +public abstract class BusinessException extends RuntimeException { + private final ErrorMessage errorMessage; + + protected BusinessException(ErrorMessage errorMessage) { + super(errorMessage.getMessage()); + this.errorMessage = errorMessage; + } + + public String getCode() { + return errorMessage.getCode(); + } +} diff --git a/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java b/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java new file mode 100644 index 00000000..d813fd09 --- /dev/null +++ b/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java @@ -0,0 +1,11 @@ +package com.example.domaintoken.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.domaintoken.entity.Token; + +public interface TokenRepository extends JpaRepository { + Optional findByUserId(Long userId); +} diff --git a/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java b/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java new file mode 100644 index 00000000..4eda87c2 --- /dev/null +++ b/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java @@ -0,0 +1,33 @@ +package com.example.infrastorage.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +@Configuration +public class AwsS3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client)AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + +} diff --git a/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java b/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java new file mode 100644 index 00000000..20d9abf0 --- /dev/null +++ b/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java @@ -0,0 +1,57 @@ +package com.example.infrastorage.s3; + +import java.io.InputStream; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.ObjectMetadata; + +import io.github.resilience4j.bulkhead.annotation.Bulkhead; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class S3Service { + private final AmazonS3Client amazonS3Client; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public record S3UploadResult(String key, String url) { + } + + @Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL) + @Async("s3UploadExecutor") + public CompletableFuture upload(String type, Long refId, MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) + try (InputStream inputStream = file.getInputStream()) { + String key = createFileKey(type, refId, file.getOriginalFilename()); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + + amazonS3Client.putObject(bucket, key, inputStream, metadata); + String url = amazonS3Client.getUrl(bucket, key).toString(); + + return CompletableFuture.completedFuture(new S3UploadResult(key, url)); + } catch (Exception e) { + throw new RuntimeException("S3 업로드 실패", e); + } + } + + public void delete(String filename) { + try { + amazonS3Client.deleteObject(bucket, filename); + } catch (Exception e) { + throw new RuntimeException("S3 파일 삭제 실패", e); + } + } + + private String createFileKey(String type, Long refId, String filename) { + return type + "/" + refId + "/" + UUID.randomUUID() + "-" + filename; + } +} diff --git a/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java b/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java new file mode 100644 index 00000000..34b293b5 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java @@ -0,0 +1,28 @@ +package com.nowait.auth.config; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +public class CorsConfig { + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + + config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 + config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:8083")); // 허용할 출처 설정 + config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 + config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 + config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 + // config.setAllowCredentials(true); // 쿠키 포함 허용 + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 + return source; + } +} diff --git a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java b/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java new file mode 100644 index 00000000..aa535f8c --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java @@ -0,0 +1,93 @@ +package com.nowait.auth.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfigurationSource; + +import com.nowait.auth.jwt.JwtAuthorizationFilter; +import com.nowait.auth.jwt.JwtUtil; +import com.nowait.auth.oauth2.CustomOAuth2UserService; +import com.nowait.auth.oauth2.OAuth2LoginSuccessHandler; +import com.nowait.auth.service.CustomUserDetailService; + +import lombok.RequiredArgsConstructor; + +@Configuration +@EnableWebSecurity // security 활성화 어노테이션 +@RequiredArgsConstructor +public class SecurityConfig { + private final CustomOAuth2UserService customOAuth2UserService; + private final OAuth2LoginSuccessHandler OAuth2LoginSuccessHandler; + private final JwtUtil jwtUtil; + private final CustomUserDetailService userDetailsService; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) + .csrf(AbstractHttpConfigurer::disable) + // 시큐리티 폼 로그인 비활성화 + .formLogin(AbstractHttpConfigurer::disable) + // HTTP Basic 인증 비활성화 + .httpBasic(AbstractHttpConfigurer::disable) + // oauth2 로그인 + // - userInfoEndPoint에서 사용자 정보 불러오고, + // - successHandler에서 로그인 성공 시 JWT 생성 및 반환로직 + .oauth2Login(oauth2 -> + oauth2.userInfoEndpoint(userInfoEndpoint -> + userInfoEndpoint.userService(customOAuth2UserService) + ).successHandler(OAuth2LoginSuccessHandler) + ) + // 세션 사용하지 않음 + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers( + "/oauth2/authorization/kakao", // 카카오 로그인 요청 + "/login/oauth2/code/**", // 카카오 인증 콜백 + "/api/refresh-token", // refresh token (토큰 갱신) + "orders/**", // 주문 관련 API + "/v1/menus/**", + "/v1/stores/**", + "/api/users/signup", + "/api/users/login", + "/swagger-ui/**", + "/v3/api-docs/**", + "/v3/api-docs.json", + "/api-docs/**", + "/swagger-resources/**", + "/webjars/**", + "/demo-ui.html" + ) + .permitAll() + .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 + ) + // JWTFiler + .addFilterBefore(new JwtAuthorizationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); + authenticationProvider.setUserDetailsService(userDetailsService); + authenticationProvider.setPasswordEncoder(passwordEncoder()); + return authenticationProvider; + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java b/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java new file mode 100644 index 00000000..af969966 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java @@ -0,0 +1,60 @@ +package com.nowait.auth.dto; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import com.nowait.user.entity.User; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CustomOAuth2User implements OAuth2User { + private User user; + + // User 객체를 받는 생성자 + public CustomOAuth2User(User user) { + this.user = user; + } + + @Override + public Map getAttributes() { + return null; + } + + // 사용자가 가지는 권한 설정 + @Override + public Collection getAuthorities() { + Collection authorities = new ArrayList<>(); + + authorities.add(new GrantedAuthority() { + @Override + public String getAuthority() { + return user.getRole().getName(); // 유저의 권한 리턴 + } + }); + + return authorities; + } + + @Override + public String getName() { + return user.getEmail(); + } + + public User getUser() { + return user; + } + + public Long getUserId() { + return user.getId(); + } + + public String getNickname() { + return user.getNickname(); + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java b/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java new file mode 100644 index 00000000..e745bd26 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java @@ -0,0 +1,40 @@ +package com.nowait.auth.dto; + +import java.util.Map; + +import lombok.RequiredArgsConstructor; + +// 카카오 OAuth2 응답에서 필요한 정보를 추출하는 역할 +@RequiredArgsConstructor +public class KaKaoResponse implements OAuth2Response { + private final Map attributes; + + @Override + public String getProvider() { + return "kakao"; + } + + @Override + public String getProviderId() { + return attributes.get("id").toString(); + } + + @Override + public String getEmail() { + Map kakaoAccount = (Map)attributes.get("kakao_account"); + return kakaoAccount.get("email").toString(); + } + + @Override + public String getNickName() { + Map properties = (Map)attributes.get("properties"); + return properties.get("nickname").toString(); + } + + @Override + public String getProfileImage() { + Map properties = (Map)attributes.get("properties"); + return properties.get("profile_image").toString(); + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java b/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java new file mode 100644 index 00000000..de987480 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java @@ -0,0 +1,19 @@ +package com.nowait.auth.dto; + +public interface OAuth2Response { + // 제공자 (ex. naver, kakao) + String getProvider(); + + // 제공자에서 발급해주는 아이디 (번호) + String getProviderId(); + + // 아래 이메일, 닉네임, 프로필이미지는 내가 카카오 developers에서 발급받겠다고 신청한 정보들이다 + // 이메일 + String getEmail(); + + // 닉네임 + String getNickName(); + + // 프로필이미지 + String getProfileImage(); +} diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java b/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java new file mode 100644 index 00000000..97a341d3 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java @@ -0,0 +1,93 @@ +package com.nowait.auth.jwt; + +import java.io.IOException; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.user.entity.User; + +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// JWT 검증 필터 +// 1. 헤더에서 accessToken 추출, 2. 토큰 검증, 3. 유효하면 사용자정보를 SecurityContextHolder에 세팅 +// 그러면, 이후 컨트롤러에서 @AuthenticationPrincipal에서 저장했던 사용자 정보를 꺼내쓸 수 있음 +@RequiredArgsConstructor +@Slf4j +public class JwtAuthorizationFilter extends OncePerRequestFilter { + private final JwtUtil jwtUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String header = request.getHeader("Authorization"); + + // 인증헤더 Bearer가 없다면, 다음 필터로 넘김 + if (header == null || !header.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + + log.info("JwtAuthorizationFilter 1 "); + return; + } + + log.info("header :: {}, header.substring(7) :: {}", header, header.substring(7)); + String accessToken = header.substring(7); + + // 토큰 만료 여부 확인, 만료 시 다음 필터로 넘기지 않음 + try { + jwtUtil.isExpired(accessToken); + } catch (ExpiredJwtException e) { + + // response status code + msg + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().print("access token expired"); + + log.info("JwtAuthorizationFilter 2 "); + return; + } + + // 토큰이 accessToken 종류인지 확인 + String tokenCategory = jwtUtil.getTokenCategory(accessToken); + + if (!tokenCategory.equals("accessToken")) { + //response status code + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().print("invalid access token"); + + log.info("JwtAuthorizationFilter 3 "); + return; + } + + // userId와 role 값 추출 + Long userId = jwtUtil.getUserId(accessToken); + String roleString = jwtUtil.getRole(accessToken); + + User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg" + , SocialType.KAKAO, Role.fromString(roleString)); + + CustomOAuth2User customOAuth2User = new CustomOAuth2User(user); + + // 스프링 시큐리티 인증 토큰 생성 + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( + customOAuth2User, null, customOAuth2User.getAuthorities()); + + // 생성한 인증 정보를 SecurityContext에 설정 + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + + log.info("JwtAuthorizationFilter 4 "); + + filterChain.doFilter(request, response); + + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java b/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java new file mode 100644 index 00000000..fd0620e9 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java @@ -0,0 +1,76 @@ +package com.nowait.auth.jwt; + +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Jwts; + +@Component +public class JwtUtil { + private final SecretKey secretKey; + + // 시크릿 키를 암호화하여, 키 생성 + public JwtUtil(@Value("${jwt.secret}") String secret) { + this.secretKey = new SecretKeySpec( + secret.getBytes(StandardCharsets.UTF_8), + Jwts.SIG.HS256.key().build().getAlgorithm() + ); + } + + public String createAccessToken(String tokenCategory, Long userId, String role, Long expiredMs) { + return Jwts.builder() + .claim("tokenCategory", tokenCategory) // accessToken + .claim("userId", userId) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } + + public String createRefreshToken(String tokenCategory, Long userId, Long expiredMs) { + return Jwts.builder() + .claim("tokenCategory", tokenCategory) // refreshToken + .claim("userId", userId) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } + + public String getTokenCategory(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseClaimsJws(token) + .getBody() + .get("tokenCategory", String.class); + } + + public String getRole(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseClaimsJws(token) + .getBody() + .get("role", String.class); + } + + public Long getUserId(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseClaimsJws(token) + .getBody() + .get("userId", Long.class); + } + + public Boolean isExpired(String token) { + return Jwts.parser().verifyWith(secretKey).build() + .parseSignedClaims(token) + .getPayload() + .getExpiration() + .before(new Date()); + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java b/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java new file mode 100644 index 00000000..b51577f7 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java @@ -0,0 +1,70 @@ +package com.nowait.auth.oauth2; + +import java.util.Optional; + +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowait.auth.dto.KaKaoResponse; +import com.nowait.auth.dto.OAuth2Response; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// OAuth2 제공자(카카오)로부터 제공받은 사용자 정보를, 우리 서비스에 맞게 가공, 변환 +@Service +@RequiredArgsConstructor +@Slf4j +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + private final UserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + log.info("CustomOAuth2UserService :: {}", oAuth2User); + log.info("oAuthUser.getAttributes :: {}", oAuth2User.getAttributes()); + + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + OAuth2Response oAuth2Response = null; + + if (registrationId.equals("kakao")) { + oAuth2Response = new KaKaoResponse(oAuth2User.getAttributes()); + } else { + throw new OAuth2AuthenticationException("지원하지 않는 OAuth2 Provider 입니다."); + } + + // DB에 유저가 있는지 판단 + Optional foundUser = userRepository.findByEmail(oAuth2Response.getEmail()); + + // DB에 유저 없으면 - 회원가입 + if (foundUser.isEmpty()) { + + User user = User.builder() + .email(oAuth2Response.getEmail()) + .nickname(oAuth2Response.getNickName()) + .profileImage(oAuth2Response.getProfileImage()) + .socialType(SocialType.KAKAO) + .role(Role.USER) // 일반 유저 설정 + .build(); + + userRepository.save(user); + + return new CustomOAuth2User(user); + } else { + // DB에 유저 존재하면 - 로그인 진행 (이때 로그인 처리는 안하고, OAuth2LoginSuccessHandler에서 담당함) + User user = foundUser.get(); + + return new CustomOAuth2User(user); + } + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java b/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java new file mode 100644 index 00000000..9364e1cc --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java @@ -0,0 +1,80 @@ +package com.nowait.auth.oauth2; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import com.example.domaintoken.entity.Token; +import com.example.domaintoken.repository.TokenRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nowait.auth.dto.CustomOAuth2User; +import com.nowait.auth.jwt.JwtUtil; +import com.nowait.user.entity.User; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// 카카오 로그인 성공 시, 콜백 핸들러 +// 1. JWT 토큰 발급 +// - 이때, JWT payload는 보안상 최소한의 정보(userId, role)만 담겠다 +// 2. refreshToken만 DB에 저장 +// 3. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. +@Component +@RequiredArgsConstructor +@Slf4j +public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + private final JwtUtil jwtUtil; + private final TokenRepository tokenRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + + // 1. CustomOAuth2UserService에서 설정한 OAuth2User 정보 가져오기 + CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); + + User user = customUserDetails.getUser(); + Long userId = customUserDetails.getUserId(); + String email = customUserDetails.getName(); + + Collection authorities = authentication.getAuthorities(); + Iterator iterator = authorities.iterator(); + GrantedAuthority auth = iterator.next(); + + String role = auth.getAuthority(); + + log.info("user, userId, email, role :: {} {} {} {}", user, userId, email, role); + + // 2. 1)의 사용자 정보를 담아, accessToken과 refreshToken 발행 + String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 60 * 60 * 1000L); // 유효기간 1시간 + String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, + 30 * 24 * 60 * 60 * 1000L); // 유효기간 30일 + + // 3. refreshToken을 DB에 저장 + Token refreshTokenEntity = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); + tokenRepository.save(refreshTokenEntity); + + // 4. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + + ObjectMapper objectMapper = new ObjectMapper(); // 객체 -> json 문자열로 변환 + String body = objectMapper.writeValueAsString( + Map.of( + "accessToken", accessToken, + "refreshToken", refreshToken + ) + ); + response.getWriter().write(body); + } + +} diff --git a/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java b/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java new file mode 100644 index 00000000..4476c9c4 --- /dev/null +++ b/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java @@ -0,0 +1,30 @@ +package com.nowait.auth.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +import com.nowait.user.entity.MemberDetails; +import com.nowait.user.entity.User; +import com.nowait.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CustomUserDetailService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + User user = userRepository.findByEmail(email).orElseThrow(); + return MemberDetails.create(user); + } + + public UserDetails loadUserById(Long id) throws UsernameNotFoundException { + User member = userRepository.findById(id).orElseThrow(); + return MemberDetails.create(member); + } +} diff --git a/security-front/src/main/java/com/nowait/exception/BusinessException.java b/security-front/src/main/java/com/nowait/exception/BusinessException.java new file mode 100644 index 00000000..60eec165 --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/BusinessException.java @@ -0,0 +1,14 @@ +package com.nowait.exception; + +public abstract class BusinessException extends RuntimeException { + private final ErrorMessage errorMessage; + + protected BusinessException(ErrorMessage errorMessage) { + super(errorMessage.getMessage()); + this.errorMessage = errorMessage; + } + + public String getCode() { + return errorMessage.getCode(); + } +} diff --git a/security-front/src/main/java/com/nowait/exception/ErrorMessage.java b/security-front/src/main/java/com/nowait/exception/ErrorMessage.java new file mode 100644 index 00000000..c365a92e --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/ErrorMessage.java @@ -0,0 +1,22 @@ +package com.nowait.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ErrorMessage { + // global + INVALID_INPUT_VALUE("입력값이 올바르지 않습니다.", "global001"), + + // auth + UNAUTHORIZED("권한이 없습니다", "auth001"), + + // token + REFRESH_TOKEN_NOT_FOUND("기존 리프레시 토큰을 찾을 수 없습니다.", "token001"), + DOES_NOT_MATCH_REFRESH_TOKEN("기존 리프레시 토큰이 일치하지 않습니다.", "token002"); + + private final String message; + private final String code; + +} diff --git a/security-front/src/main/java/com/nowait/exception/ErrorResponse.java b/security-front/src/main/java/com/nowait/exception/ErrorResponse.java new file mode 100644 index 00000000..a15935e8 --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/ErrorResponse.java @@ -0,0 +1,26 @@ +package com.nowait.exception; + +import java.util.HashMap; +import java.util.Map; + +import lombok.Getter; + +@Getter +public class ErrorResponse { + private final String message; + private final String code; + private final Map errors; + + public ErrorResponse(String message, String code) { + this.message = message; + this.code = code; + errors = new HashMap<>(); + } + + public ErrorResponse(String message, String code, Map errors) { + this.message = message; + this.code = code; + this.errors = errors; + } + +} diff --git a/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java b/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..b9f7c1e2 --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java @@ -0,0 +1,108 @@ +package com.nowait.exception; + +import static com.nowait.exception.ErrorMessage.*; +import static org.springframework.http.HttpStatus.*; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingRequestValueException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MultipartException; + +import io.swagger.v3.oas.annotations.Hidden; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Hidden +@RestControllerAdvice +public class GlobalExceptionHandler { + + // OAUTH 인증 실패 에러처리 메서드 + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(OAuth2AuthenticationException.class) + public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e) { + log.error("handleOAuth2AuthenticationException", e); + + return new ErrorResponse("OAuth 인증 실패 : " + e.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(BusinessException.class) + public ErrorResponse handleBusinessException(BusinessException e) { + log.error("handleBusinessException", e); + return new ErrorResponse(e.getMessage(), e.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error("handleMethodArgumentNotValidException", e); + Map errors = getErrors(e); + return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(HttpMessageNotReadableException.class) + public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.error("handleHttpMessageNotReadableException", e); + return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(IllegalArgumentException.class) + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { + log.error("handleIllegalArgumentException", e); + return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(MissingRequestValueException.class) + public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { + log.error("handleMissingRequestValueExceptionException", e); + return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + @ResponseStatus(value = UNAUTHORIZED) + @ExceptionHandler(UnauthorizedException.class) + public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { + log.error("handleUnauthorizedExceptionException", e); + return new ErrorResponse(e.getMessage(), e.getCode()); + } + + @ResponseStatus(value = NOT_FOUND) + @ExceptionHandler(ResourceNotFoundException.class) + public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { + log.error("handleResourceNotFoundExceptionException", e); + return new ErrorResponse(e.getMessage(), e.getCode()); + } + + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(MultipartException.class) + public ErrorResponse handleMultipartException(MultipartException e) { + log.error("handleMultipartException", e); + return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); + } + + + private static Map getErrors(MethodArgumentNotValidException e) { + return e.getBindingResult() + .getAllErrors() + .stream() + .filter(ObjectError.class::isInstance) + .collect(Collectors.toMap( + error -> error instanceof FieldError ? ((FieldError)error).getField() : error.getObjectName(), + ObjectError::getDefaultMessage, + (msg1, msg2) -> msg1 + ";" + msg2 + )); + } + +} diff --git a/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java b/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java new file mode 100644 index 00000000..5365ef52 --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.exception; + +public class RefreshTokenNotFoundException extends ResourceNotFoundException { + + public RefreshTokenNotFoundException() { + super(ErrorMessage.REFRESH_TOKEN_NOT_FOUND); + } + +} diff --git a/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java b/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java new file mode 100644 index 00000000..ae4e4b7b --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java @@ -0,0 +1,14 @@ +package com.nowait.exception; + +public abstract class ResourceNotFoundException extends RuntimeException { + private final ErrorMessage errorMessage; + + protected ResourceNotFoundException(ErrorMessage errorMessage) { + super(errorMessage.getMessage()); + this.errorMessage = errorMessage; + } + + public String getCode() { + return errorMessage.getCode(); + } +} diff --git a/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java b/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java new file mode 100644 index 00000000..504852ee --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java @@ -0,0 +1,8 @@ +package com.nowait.exception; + +public class TokenBadRequestException extends BusinessException { + public TokenBadRequestException() { + super(ErrorMessage.DOES_NOT_MATCH_REFRESH_TOKEN); + } + +} diff --git a/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java b/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java new file mode 100644 index 00000000..65a8fd0a --- /dev/null +++ b/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java @@ -0,0 +1,19 @@ +package com.nowait.exception; + +public class UnauthorizedException extends RuntimeException { + private final ErrorMessage errorMessage; + + public UnauthorizedException() { + super(ErrorMessage.UNAUTHORIZED.getMessage()); + this.errorMessage = ErrorMessage.UNAUTHORIZED; + } + + public UnauthorizedException(ErrorMessage errorMessage) { + super(errorMessage.getMessage()); + this.errorMessage = errorMessage; + } + + public String getCode() { + return errorMessage.getCode(); + } +} From e8351c08db10ac9e4f6703e47679b79e6664014f Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 3 Jul 2025 18:07:01 +0900 Subject: [PATCH 083/685] =?UTF-8?q?fix(Bookmark,user):=EC=98=A4=ED=83=80?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B5=AC=EC=B2=B4=EC=A0=81=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - jwt 토큰 에러 로그 구체화 - bookmark 참조비교에서 값비교로 변경 --- .../token/service/TokenService.java | 10 +++++----- .../bookmark/service/BookmarkService.java | 3 ++- .../com/nowait/auth/config/SecurityConfig.java | 2 +- .../com/nowait/auth/dto/CustomOAuth2User.java | 9 ++------- .../nowait/auth/jwt/JwtAuthorizationFilter.java | 15 ++++++++++----- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java b/application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java index 3ba54b63..746b86cb 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java @@ -23,29 +23,29 @@ public class TokenService { private final JwtUtil jwtUtil; @Transactional - public Boolean validateToken(String token, Long userId){ + public boolean validateToken(String token, Long userId){ // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. Optional savedToken = tokenRepository.findByUserId(userId); // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 if (savedToken.isEmpty()){ - log.info("여기에 걸렸니 ? -- 1 "); + log.debug("DB에 현재 userId에 대응되는 리프레시 토큰이 없습니다"); return false; } // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 if (!savedToken.get().getRefreshToken().equals(token)){ - log.info("여기에 걸렸니 ? -- 2 "); + log.debug("DB에 저장된 리프레시 토큰와 현재 전달받은 리프레시 토큰 일치하지 않습니다"); return false; } // 리프레시 토큰의 만료여부 확인 if(jwtUtil.isExpired(token)){ - log.info("여기에 걸렸니 ? -- 3 "); + log.debug("리프레시 토큰이 만료된 토큰입니다"); return false; // 만료된 토큰은 유효하지 않음 } - log.info("여기에 걸렸니 ? -- 4 "); + log.info("리프레시 토큰이 유효한 토큰입니다"); return true; // 모든 조건 만족 시, 유효한 토큰 } diff --git a/application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 5aae6553..31fdc89e 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -1,6 +1,7 @@ package com.nowait.applicationuser.bookmark.service; import java.util.List; +import java.util.Objects; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -59,7 +60,7 @@ public String deleteBookmark(Long bookmarkId, CustomOAuth2User customOAuth2User) parameterValidation(bookmarkId, customOAuth2User); Bookmark bookmark = bookmarkRepository.findById(bookmarkId) .orElseThrow(() -> new EntityNotFoundException(bookmarkId + " bookmark not found.")); - if (bookmark.getUser().getId() != customOAuth2User.getUserId()) { + if (!Objects.equals(bookmark.getUser().getId(), customOAuth2User.getUserId())) { throw new IllegalArgumentException("you can only delete your own bookmark"); } bookmarkRepository.delete(bookmark); diff --git a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java b/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java index aa535f8c..c4ebb970 100644 --- a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java +++ b/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java @@ -57,7 +57,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/oauth2/authorization/kakao", // 카카오 로그인 요청 "/login/oauth2/code/**", // 카카오 인증 콜백 "/api/refresh-token", // refresh token (토큰 갱신) - "orders/**", // 주문 관련 API + "/orders/**", // 주문 관련 API "/v1/menus/**", "/v1/stores/**", "/api/users/signup", diff --git a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java b/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java index af969966..61432393 100644 --- a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java +++ b/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java @@ -13,16 +13,11 @@ @RequiredArgsConstructor public class CustomOAuth2User implements OAuth2User { - private User user; - - // User 객체를 받는 생성자 - public CustomOAuth2User(User user) { - this.user = user; - } + private final User user; @Override public Map getAttributes() { - return null; + return Map.of("email", user.getEmail(), "nickname", user.getNickname()); } // 사용자가 가지는 권한 설정 diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java b/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java index 97a341d3..dcbbd985 100644 --- a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java +++ b/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java @@ -36,7 +36,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (header == null || !header.startsWith("Bearer ")) { filterChain.doFilter(request, response); - log.info("JwtAuthorizationFilter 1 "); + log.debug("JwtAuthorizationFilter: Authorization 헤더가 없거나 Bearer 토큰 형식이 아님. JWT 인증 필터를 건너뜁니다. [header={}] ", header); return; } @@ -52,7 +52,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().print("access token expired"); - log.info("JwtAuthorizationFilter 2 "); + log.warn("JwtAuthorizationFilter: 만료된 AccessToken입니다. 토큰 인증 거부, URI: {}", request.getRequestURI()); return; } @@ -64,14 +64,19 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().print("invalid access token"); - log.info("JwtAuthorizationFilter 3 "); + log.warn("JwtAuthorizationFilter: 잘못된 토큰 유형(accessToken 아님)으로 인증 요청. URI: {}, tokenCategory: {}", request.getRequestURI(), tokenCategory); return; } // userId와 role 값 추출 Long userId = jwtUtil.getUserId(accessToken); String roleString = jwtUtil.getRole(accessToken); - + if (userId == null || roleString == null) { + log.warn("JwtAuthorizationFilter: JWT에서 userId 또는 role 추출 실패. 토큰: {}", accessToken); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().print("invalid token"); + return; + } User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg" , SocialType.KAKAO, Role.fromString(roleString)); @@ -84,7 +89,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // 생성한 인증 정보를 SecurityContext에 설정 SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); - log.info("JwtAuthorizationFilter 4 "); + log.info("JwtAuthorizationFilter: 인증 성공. userId={}, role={}, URI={}", userId, roleString, request.getRequestURI()); filterChain.doFilter(request, response); From 4d432d3692cab67121240acdbcd080c619e4024f Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 3 Jul 2025 19:58:10 +0900 Subject: [PATCH 084/685] =?UTF-8?q?feat(order):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A3=BC=EB=AC=B8=EC=A1=B0=ED=9A=8C/=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - entity에 swagger 의존성 추가 - order 주문,수정 로직 구현 --- .../order/controller/OrderController.java | 49 +++++++++++++++++-- .../order/dto/OrderResponseDto.java | 33 +++++++++++++ .../dto/OrderStatusUpdateRequestDto.java | 16 ++++++ .../dto/OrderStatusUpdateResponseDto.java | 17 +++++++ .../order/service/OrderService.java | 37 ++++++++++++++ .../order/dto/OrderCreateRequestDto.java | 3 ++ .../order/dto/OrderCreateResponseDto.java | 5 ++ .../order/service/OrderService.java | 4 ++ domain-order/build.gradle | 3 ++ .../com/nowait/order/entity/OrderStatus.java | 22 +++++++++ .../com/nowait/order/entity/UserOrder.java | 13 +++++ .../order/repository/OrderRepository.java | 1 + .../exception/BusinessException.java | 2 +- 13 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java create mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java create mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java create mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java create mode 100644 domain-order/src/main/java/com/nowait/order/entity/OrderStatus.java diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index c1a25862..775306e8 100644 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -1,22 +1,61 @@ package com.nowait.applicationadmin.order.controller; +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationadmin.order.dto.OrderResponseDto; +import com.nowait.applicationadmin.order.dto.OrderStatusUpdateRequestDto; +import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; +import com.nowait.applicationadmin.order.service.OrderService; +import com.nowait.common.api.ApiUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @Tag(name = "Order API", description = "주문 API") @RestController -@RequestMapping("admin//orders") +@RequestMapping("admin/orders") @RequiredArgsConstructor public class OrderController { - @GetMapping - public void getOrders() { - //TODO - } + private final OrderService orderService; + @GetMapping("/{storeId}") + @Operation(summary = "주점별 주문리스트 조회", description = "특정 주점에 대한 예약리스트 조회") + @ApiResponse(responseCode = "200", description = "주리스트 조회") + public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId) { + List response = orderService.findAllOrders(storeId); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + ); + } + @PatchMapping("/status/{orderId}") + @Operation(summary = "주문 상태 변경", description = "특정 주문의 상태를 변경.") + @ApiResponse(responseCode = "200", description = "주문 상태 변경 성공") + @ApiResponse(responseCode = "400", description = "주문을 찾을 수 없음") + public ResponseEntity updateOrderStatus( + @PathVariable Long orderId, + @RequestBody@Valid OrderStatusUpdateRequestDto requestDto + ) { + OrderStatusUpdateResponseDto response = orderService.updateOrderStatus(orderId, requestDto.getOrderStatus()); + return ResponseEntity + .status(HttpStatus.OK) + .body(ApiUtils.success(response)); + } } diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java new file mode 100644 index 00000000..afb2a079 --- /dev/null +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -0,0 +1,33 @@ +package com.nowait.applicationadmin.order.dto; + +import java.time.LocalDateTime; + +import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; +import com.nowait.order.entity.UserOrder; +import com.nowait.order.entity.OrderStatus; +import com.nowait.reservation.entity.Reservation; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class OrderResponseDto { + private Long id; + private Long tableId; + private String depositorName; + private Integer totalPrice; + private OrderStatus status; + private LocalDateTime createdAt; + + public static OrderResponseDto fromEntity(UserOrder userOrder) { + return OrderResponseDto.builder() + .id(userOrder.getId()) + .tableId(userOrder.getTableId()) + .depositorName(userOrder.getDepositorName()) + .totalPrice(userOrder.getTotalPrice()) + .status(userOrder.getStatus()) + .createdAt(userOrder.getCreatedAt()) + .build(); + } +} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java new file mode 100644 index 00000000..a8a78442 --- /dev/null +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java @@ -0,0 +1,16 @@ +package com.nowait.applicationadmin.order.dto; + +import com.nowait.order.entity.OrderStatus; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class OrderStatusUpdateRequestDto { + @Schema(description = "주문 상태", example = "WAITING_FOR_PAYMENT", allowableValues = {"WAITING_FOR_PAYMENT", "COOKING", "COOKED"}) + @NotNull(message = "주문상태는 필수입니다") + private final OrderStatus orderStatus; +} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java new file mode 100644 index 00000000..63de5b53 --- /dev/null +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java @@ -0,0 +1,17 @@ +package com.nowait.applicationadmin.order.dto; + +import com.nowait.order.entity.OrderStatus; +import com.nowait.order.entity.UserOrder; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class OrderStatusUpdateResponseDto { + private final OrderStatus orderStatus; + + public static OrderStatusUpdateResponseDto fromEntity(UserOrder userOrder) { + return new OrderStatusUpdateResponseDto(userOrder.getStatus()); + } +} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java new file mode 100644 index 00000000..c06986cf --- /dev/null +++ b/application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -0,0 +1,37 @@ +package com.nowait.applicationadmin.order.service; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationadmin.order.dto.OrderResponseDto; +import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; +import com.nowait.order.entity.OrderStatus; +import com.nowait.order.entity.UserOrder; +import com.nowait.order.repository.OrderRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OrderService { + + private final OrderRepository orderRepository; + + @Transactional(readOnly = true) + public List findAllOrders(Long storeId) { + return orderRepository.findAllByStore_StoreId(storeId).stream() + .map(OrderResponseDto::fromEntity) + .collect(Collectors.toList()); + } + + @Transactional + public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus) { + UserOrder userOrder = orderRepository.findById(orderId) + .orElseThrow(() -> new IllegalArgumentException("Order not found with id: " + orderId)); + userOrder.updateStatus(newStatus); + return OrderStatusUpdateResponseDto.fromEntity(userOrder); + } +} diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java index 8823d0e4..50c6795f 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java @@ -15,6 +15,9 @@ public class OrderCreateRequestDto { @NotBlank(message = "주문자 이름은 필수입니다") @Size(max = 10, message = "주문자 이름은 10자 이하여야 합니다") private final String depositorName; // 예약자 이름 + @NotBlank(message = "주문 내역은 필수입니다") private final List items; // 장바구니 항목 리스트 + @NotBlank(message = "주문금액은 필수 입니다.") + private final int totalPrice; } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index 89a0e5e5..6044303e 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -2,6 +2,7 @@ import java.util.List; +import com.nowait.order.entity.OrderStatus; import com.nowait.order.entity.UserOrder; import lombok.AllArgsConstructor; @@ -18,6 +19,8 @@ public class OrderCreateResponseDto { private String sessionId; private String depositorName; private List orderItems; // 주문 항목 목록 + private OrderStatus status; + private Integer totalPrice; public static OrderCreateResponseDto fromEntity(UserOrder order) { return OrderCreateResponseDto.builder() @@ -27,6 +30,8 @@ public static OrderCreateResponseDto fromEntity(UserOrder order) { .sessionId(order.getSessionId()) .depositorName(order.getDepositorName()) .orderItems(List.of()) + .status(order.getStatus()) + .totalPrice(order.getTotalPrice()) .build(); } } diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 35ffdde4..a4835e61 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -15,6 +15,7 @@ import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; +import com.nowait.order.entity.OrderStatus; import com.nowait.store.entity.Store; import com.nowait.store.repository.StoreRepository; import com.nowait.menu.entity.Menu; @@ -56,6 +57,9 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId, .signature(signature) // signature 저장 .sessionId(sessionId) // sessionId 저장 .depositorName(orderCreateRequestDto.getDepositorName()) + .status(OrderStatus.WAITING_FOR_PAYMENT) + .totalPrice(orderCreateRequestDto.getTotalPrice()) + .build(); UserOrder savedOrder = orderRepository.save(order); diff --git a/domain-order/build.gradle b/domain-order/build.gradle index 263db1df..7c7124e7 100644 --- a/domain-order/build.gradle +++ b/domain-order/build.gradle @@ -29,4 +29,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' } diff --git a/domain-order/src/main/java/com/nowait/order/entity/OrderStatus.java b/domain-order/src/main/java/com/nowait/order/entity/OrderStatus.java new file mode 100644 index 00000000..7a111111 --- /dev/null +++ b/domain-order/src/main/java/com/nowait/order/entity/OrderStatus.java @@ -0,0 +1,22 @@ +package com.nowait.order.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "주문 상태 Enum") +public enum OrderStatus { + @Schema(description = "입금대기") + WAITING_FOR_PAYMENT("입금대기"), + + @Schema(description = "조리중") + COOKING("조리중"), + + @Schema(description = "조리완료") + COOKED("조리완료"); + + private final String description; +} + diff --git a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java index 06909f2c..a1c27498 100644 --- a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java +++ b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java @@ -9,6 +9,8 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -43,10 +45,21 @@ public class UserOrder extends BaseTimeEntity { private Store store; @OneToMany(mappedBy = "userOrder", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default private List orderItems = new ArrayList<>(); private String sessionId; @Column(length = 10) // 예약자 이름 길이 제한 private String depositorName; + @Builder.Default + @Enumerated(value = EnumType.STRING) + private OrderStatus status = OrderStatus.WAITING_FOR_PAYMENT; + + private Integer totalPrice; + + public void updateStatus(OrderStatus newStatus) { + this.status = newStatus; + } + } diff --git a/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java b/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java index e41b80de..48ca22d4 100644 --- a/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java +++ b/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java @@ -13,6 +13,7 @@ public interface OrderRepository extends JpaRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); + List findAllByStore_StoreId(Long storeId); } diff --git a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java b/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java index 05dc5c67..7fe9c748 100644 --- a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java +++ b/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java @@ -1,6 +1,6 @@ package com.example.domaintoken.exception; -import com.nowaiting.common.exception.ErrorMessage; +import com.nowait.common.exception.ErrorMessage; public abstract class BusinessException extends RuntimeException { private final ErrorMessage errorMessage; From 9828345cb5544dcc2469b065438251a37e85b4d3 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 3 Jul 2025 20:18:19 +0900 Subject: [PATCH 085/685] =?UTF-8?q?fix:=20=EC=A4=91=EB=B3=B5=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 삭제된 디렉토리가 중복 등장 / 삭제처리 --- .../example/apiuser/ApiUserApplication.java | 39 ---- .../controller/BookmarkController.java | 67 ------- .../bookmark/dto/BookmarkCreateResponse.java | 24 --- .../bookmark/dto/BookmarkGetResponse.java | 24 --- .../bookmark/service/BookmarkService.java | 78 -------- .../exception/GlobalExceptionHandler.java | 170 ------------------ .../menu/controller/MenuController.java | 32 ---- .../menu/dto/MenuImageUploadResponse.java | 20 --- .../example/apiuser/menu/dto/MenuReadDto.java | 32 ---- .../apiuser/menu/dto/MenuReadResponse.java | 21 --- .../apiuser/menu/service/MenuService.java | 42 ----- .../order/controller/OrderController.java | 70 -------- .../apiuser/order/dto/CartItemDto.java | 11 -- .../order/dto/OrderCreateRequestDto.java | 15 -- .../order/dto/OrderCreateResponseDto.java | 28 --- .../dto/OrderItemListGetResponseDto.java | 29 --- .../order/dto/OrderItemResponseDTO.java | 16 -- .../apiuser/order/service/OrderService.java | 128 ------------- .../controller/ReservationController.java | 47 ----- .../dto/ReservationCreateRequestDto.java | 10 -- .../dto/ReservationCreateResponseDto.java | 17 -- .../ReservationNotFoundException.java | 9 - .../service/ReservationService.java | 58 ------ .../store/controller/StoreController.java | 56 ------ .../store/dto/StoreImageUploadResponse.java | 22 --- .../apiuser/store/dto/StoreReadDto.java | 39 ---- .../apiuser/store/dto/StoreReadResponse.java | 23 --- .../apiuser/store/service/StoreService.java | 16 -- .../store/service/StoreServiceImpl.java | 73 -------- .../token/controller/TokenController.java | 59 ------ .../token/dto/AuthenticationResponse.java | 18 -- .../token/dto/RefreshTokenRequest.java | 12 -- .../apiuser/token/service/TokenService.java | 66 ------- .../order/dto/OrderCreateRequestDto.java | 8 +- .../com/nowait/order/entity/UserOrder.java | 1 + 35 files changed, 7 insertions(+), 1373 deletions(-) delete mode 100644 api-user/src/main/java/com/example/apiuser/ApiUserApplication.java delete mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java delete mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java delete mode 100644 api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java delete mode 100644 api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java delete mode 100644 api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java delete mode 100644 api-user/src/main/java/com/example/apiuser/order/service/OrderService.java delete mode 100644 api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java delete mode 100644 api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java delete mode 100644 api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java delete mode 100644 api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java delete mode 100644 api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java delete mode 100644 api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/store/service/StoreService.java delete mode 100644 api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java delete mode 100644 api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java delete mode 100644 api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java delete mode 100644 api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java delete mode 100644 api-user/src/main/java/com/example/apiuser/token/service/TokenService.java diff --git a/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java b/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java deleted file mode 100644 index fcaf441e..00000000 --- a/api-user/src/main/java/com/example/apiuser/ApiUserApplication.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.apiuser; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@EnableJpaAuditing -@SpringBootApplication(scanBasePackages = { - "com.example.apiuser", - "com.nowait.auth" -}) -@EntityScan(basePackages = { - "com.example.menu.entity", // domain-menu - "com.example.domainstore.entity", // domain-store - "com.example.domaintoken.entity", - "com.nowaiting.user.entity", - "com.nowait.domainbookmark.entity", - "com.nowait.domainreservation.entity", - "com.nowait.domainorder.entity", - "com.nowait.domainorder.entity" -}) -@EnableJpaRepositories(basePackages = { - "com.example.menu.repository", - "com.nowaiting.user.repository", - "com.example.domainstore.repository", - "com.example.domaintoken.repository", - "com.nowait.domainbookmark.repository", - "com.nowait.domainorder.repository", - "com.nowait.domainreservation.repository" -}) -public class ApiUserApplication { - - public static void main(String[] args) { - SpringApplication.run(ApiUserApplication.class, args); - } - -} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java b/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java deleted file mode 100644 index 32c4132a..00000000 --- a/api-user/src/main/java/com/example/apiuser/bookmark/controller/BookmarkController.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.example.apiuser.bookmark.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.apiuser.bookmark.dto.BookmarkCreateResponse; -import com.example.apiuser.bookmark.service.BookmarkService; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowaiting.common.api.ApiUtils; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -@Tag(name = "Bookmark API", description = "북마크 API") -@RestController -@RequestMapping("/bookmarks") -@RequiredArgsConstructor -public class BookmarkController { - private final BookmarkService bookmarkService; - - @PostMapping("/{storeId}") - @Operation(summary = "북마크 생성", description = "특정 주점에 대한 북마크 생성") - @ApiResponse(responseCode = "201", description = "북마크 생성") - public ResponseEntity createBookmark(@PathVariable Long storeId,@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { - BookmarkCreateResponse response = bookmarkService.createBookmark(storeId,customOAuth2User); - - return ResponseEntity - .status(HttpStatus.CREATED) - .body( - ApiUtils.success( - response - ) - ); - } - @GetMapping - @Operation(summary = "북마크 조회", description = "내가 북마크한 주점 조회") - @ApiResponse(responseCode = "200", description = "북마크 조회") - public ResponseEntity getAllBookmarks(@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { - return ResponseEntity - .ok() - .body( - ApiUtils.success( - bookmarkService.getBookmarks(customOAuth2User) - ) - ); - } - @DeleteMapping("/{bookmarkId}") - @Operation(summary = "북마크 삭제", description = "특정 주점에 대한 북마크 삭제") - @ApiResponse(responseCode = "200", description = "북마크 삭제") - public ResponseEntity deleteBookmark(@PathVariable Long bookmarkId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { - return ResponseEntity - .ok() - .body( - ApiUtils.success( - bookmarkService.deleteBookmark(bookmarkId,customOAuth2User) - ) - ); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java b/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java deleted file mode 100644 index 5e4a8f65..00000000 --- a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkCreateResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.apiuser.bookmark.dto; - -import com.nowait.domainbookmark.entity.Bookmark; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class BookmarkCreateResponse { - private Long bookmarkId; - private Long userId; - private Long storeId; - - public static BookmarkCreateResponse fromEntity(Bookmark bookmark) { - return BookmarkCreateResponse.builder() - .bookmarkId(bookmark.getId()) - .userId(bookmark.getUser().getId()) - .storeId(bookmark.getStore().getStoreId()) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java b/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java deleted file mode 100644 index 6f05db8e..00000000 --- a/api-user/src/main/java/com/example/apiuser/bookmark/dto/BookmarkGetResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.apiuser.bookmark.dto; - -import com.nowait.domainbookmark.entity.Bookmark; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class BookmarkGetResponse { - private Long bookmarkId; - private Long userId; - private Long storeId; - - public static BookmarkGetResponse fromEntity(Bookmark bookmark) { - return BookmarkGetResponse.builder() - .bookmarkId(bookmark.getId()) - .userId(bookmark.getUser().getId()) - .storeId(bookmark.getStore().getStoreId()) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java b/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java deleted file mode 100644 index 91ce0ff8..00000000 --- a/api-user/src/main/java/com/example/apiuser/bookmark/service/BookmarkService.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.example.apiuser.bookmark.service; - -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.example.apiuser.bookmark.dto.BookmarkCreateResponse; -import com.example.apiuser.bookmark.dto.BookmarkGetResponse; -import com.example.domainstore.entity.Store; -import com.example.domainstore.repository.StoreRepository; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.domainbookmark.entity.Bookmark; -import com.nowait.domainbookmark.repository.BookmarkRepository; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.repository.UserRepository; - -import jakarta.persistence.EntityNotFoundException; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class BookmarkService { - private final BookmarkRepository bookmarkRepository; - private final StoreRepository storeRepository; - private final UserRepository userRepository; - @Transactional - public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User customOAuth2User) { - parameterValidation(storeId, customOAuth2User); - Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); - User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(() -> new EntityNotFoundException("User not found")); - - if (bookmarkRepository.existsByUserAndStore(user, store)) { - throw new IllegalArgumentException("already bookmarked"); - } - - Bookmark bookmark = Bookmark.builder() - .store(store) - .user(user) - .build(); - - return BookmarkCreateResponse.fromEntity(bookmarkRepository.save(bookmark)); - } - - @Transactional(readOnly = true) - public List getBookmarks(CustomOAuth2User customOAuth2User) { - User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(() -> new EntityNotFoundException("User not found")); - return bookmarkRepository.findAllByUser(user) - .stream() - .map(BookmarkGetResponse::fromEntity) - .toList(); - } - - @Transactional - public String deleteBookmark(Long bookmarkId, CustomOAuth2User customOAuth2User) { - parameterValidation(bookmarkId, customOAuth2User); - Bookmark bookmark = bookmarkRepository.findById(bookmarkId) - .orElseThrow(() -> new EntityNotFoundException(bookmarkId + " bookmark not found.")); - if (bookmark.getUser().getId() != customOAuth2User.getUserId()) { - throw new IllegalArgumentException("you can only delete your own bookmark"); - } - bookmarkRepository.delete(bookmark); - return "Bookmark ID " + bookmarkId + " deleted."; - } - - private static void parameterValidation(Long storeId, CustomOAuth2User customOAuth2User) { - // 파라미터 유효성 검사 - if (storeId == null || storeId < 0) { - throw new IllegalArgumentException("storeId must be a positive number"); - } - if (customOAuth2User == null || customOAuth2User.getUserId() == null) { - throw new IllegalArgumentException("UserInfo is required"); - } - } -} diff --git a/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java b/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java deleted file mode 100644 index fedb029f..00000000 --- a/api-user/src/main/java/com/example/apiuser/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.example.apiuser.exception; - -import static com.nowaiting.common.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.*; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; - -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.validation.FieldError; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingRequestValueException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.multipart.MultipartException; - -import com.example.apiuser.reservation.exception.ReservationNotFoundException; -import com.nowait.domainbookmark.exception.BookmarkOwnerMismatchException; -import com.nowait.domainbookmark.exception.DuplicateBookmarkException; -import com.nowait.domainorder.exception.DuplicateOrderException; -import com.nowait.domainorder.exception.OrderItemsEmptyException; -import com.nowait.domainorder.exception.OrderParameterEmptyException; -import com.nowait.exception.BusinessException; -import com.nowait.exception.ResourceNotFoundException; -import com.nowait.exception.UnauthorizedException; -import com.nowaiting.common.exception.ErrorMessage; -import com.nowaiting.common.exception.ErrorResponse; -import com.nowaiting.user.exception.UserNotFoundException; - -import io.swagger.v3.oas.annotations.Hidden; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Hidden -@RestControllerAdvice -public class GlobalExceptionHandler { - - // OAUTH 인증 실패 에러처리 메서드 - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(OAuth2AuthenticationException.class) - public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e) { - log.error("handleOAuth2AuthenticationException", e); - - return new ErrorResponse("OAuth 인증 실패 : " + e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(BusinessException.class) - public ErrorResponse handleBusinessException(BusinessException e) { - log.error("handleBusinessException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MethodArgumentNotValidException.class) - public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - log.error("handleMethodArgumentNotValidException", e); - Map errors = getErrors(e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(HttpMessageNotReadableException.class) - public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - log.error("handleHttpMessageNotReadableException", e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(IllegalArgumentException.class) - public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { - log.error("handleIllegalArgumentException", e); - return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MissingRequestValueException.class) - public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { - log.error("handleMissingRequestValueExceptionException", e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = UNAUTHORIZED) - @ExceptionHandler(UnauthorizedException.class) - public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { - log.error("handleUnauthorizedExceptionException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = NOT_FOUND) - @ExceptionHandler(ResourceNotFoundException.class) - public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { - log.error("handleResourceNotFoundExceptionException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MultipartException.class) - public ErrorResponse handleMultipartException(MultipartException e) { - log.error("handleMultipartException", e); - return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(DuplicateBookmarkException.class) - public ErrorResponse handleDuplicateBookmarkException(DuplicateBookmarkException e) { - log.error("handleDuplicateBookmarkException", e); - return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_BOOKMARK.getCode()); - } - - @ResponseStatus(value = FORBIDDEN) - @ExceptionHandler(BookmarkOwnerMismatchException.class) - public ErrorResponse bookmarkOwnerMismatchException(BookmarkOwnerMismatchException e) { - log.error("bookmarkOwnerMismatchException", e); - return new ErrorResponse(e.getMessage(), NOT_OWN_BOOKMARK.getCode()); - } - - @ResponseStatus(value = NOT_FOUND) - @ExceptionHandler(UserNotFoundException.class) - public ErrorResponse userNotFoundException(UserNotFoundException e) { - log.error("userNotFoundException", e); - return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(OrderParameterEmptyException.class) - public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e) { - log.error("orderParameterEmptyException", e); - return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(OrderItemsEmptyException.class) - public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e) { - log.error("orderItemsEmptyException", e); - return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(DuplicateOrderException.class) - public ErrorResponse duplicateOrderException(DuplicateOrderException e) { - log.error("duplicateOrderException", e); - return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); - } - - @ResponseStatus(value = NOT_FOUND) - @ExceptionHandler(ReservationNotFoundException.class) - public ErrorResponse reservationNotFoundException(ReservationNotFoundException e) { - log.error("reservationNotFoundException", e); - return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); - } - - - private static Map getErrors(MethodArgumentNotValidException e) { - return e.getBindingResult() - .getAllErrors() - .stream() - .filter(ObjectError.class::isInstance) - .collect(Collectors.toMap( - error -> error instanceof FieldError ? ((FieldError)error).getField() : error.getObjectName(), - ObjectError::getDefaultMessage, - (msg1, msg2) -> msg1 + ";" + msg2 - )); - } - -} diff --git a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java b/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java deleted file mode 100644 index d3fb6d02..00000000 --- a/api-user/src/main/java/com/example/apiuser/menu/controller/MenuController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.apiuser.menu.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.apiuser.menu.service.MenuService; -import com.nowaiting.common.api.ApiUtils; - -import lombok.RequiredArgsConstructor; - -@RestController -@RequestMapping("/v1/menus") -@RequiredArgsConstructor -public class MenuController { - - private final MenuService menuService; - - @GetMapping("/all-menus/stores/{storeId}") - public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - menuService.getMenusByStoreId(storeId) - ) - ); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java deleted file mode 100644 index c32be356..00000000 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuImageUploadResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.apiuser.menu.dto; - -import com.example.menu.entity.MenuImage; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class MenuImageUploadResponse { - private final Long id; - private final String imageUrl; - - public static MenuImageUploadResponse fromEntity(MenuImage menuImage) { - return MenuImageUploadResponse.builder() - .id(menuImage.getId()) - .imageUrl(menuImage.getImageUrl()) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java deleted file mode 100644 index 98dda1f2..00000000 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadDto.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.apiuser.menu.dto; - -import java.util.List; - -import com.example.menu.entity.Menu; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class MenuReadDto { - private Long menuId; - private Long storeId; - private String name; - private String description; - private Integer price; - private List images; - - public static MenuReadDto fromEntity(Menu menu, List images) { - return MenuReadDto.builder() - .menuId(menu.getId()) - .storeId(menu.getStoreId()) - .name(menu.getName()) - .description(menu.getDescription()) - .price(menu.getPrice()) - .images(images) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java b/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java deleted file mode 100644 index 755b243f..00000000 --- a/api-user/src/main/java/com/example/apiuser/menu/dto/MenuReadResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.apiuser.menu.dto; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class MenuReadResponse { - - private List menuReadDto; - - public static MenuReadResponse of(List menuReadDto) { - return MenuReadResponse.builder() - .menuReadDto(menuReadDto) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java b/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java deleted file mode 100644 index 64b9e0b9..00000000 --- a/api-user/src/main/java/com/example/apiuser/menu/service/MenuService.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.apiuser.menu.service; - -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.example.apiuser.menu.dto.MenuImageUploadResponse; -import com.example.apiuser.menu.dto.MenuReadDto; -import com.example.apiuser.menu.dto.MenuReadResponse; -import com.example.menu.entity.Menu; -import com.example.menu.entity.MenuImage; -import com.example.menu.repository.MenuImageRepository; -import com.example.menu.repository.MenuRepository; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class MenuService { - - private final MenuRepository menuRepository; - private final MenuImageRepository menuImageRepository; - - - @Transactional(readOnly = true) - public MenuReadResponse getMenusByStoreId(Long storeId) { - List menus = menuRepository.findAllByStoreId(storeId); - - List menuReadResponse = menus.stream() - .map(menu -> { - List images = menuImageRepository.findByMenu(menu); - List imageDto = images.stream() - .map(MenuImageUploadResponse::fromEntity) - .toList(); - return MenuReadDto.fromEntity(menu, imageDto); - }) - .toList(); - - return MenuReadResponse.of(menuReadResponse); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java b/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java deleted file mode 100644 index 65238120..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/controller/OrderController.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.example.apiuser.order.controller; - -import java.util.List; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.apiuser.order.dto.OrderCreateRequestDto; -import com.example.apiuser.order.dto.OrderCreateResponseDto; -import com.example.apiuser.order.dto.OrderItemListGetResponseDto; -import com.example.apiuser.order.service.OrderService; -import com.nowaiting.common.api.ApiUtils; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; - -@Tag(name = "Order API", description = "주문 API") -@RestController -@RequestMapping("/orders") -@RequiredArgsConstructor -public class OrderController { - private final OrderService orderService; - - @PostMapping("/create/{storeId}/{tableId}") - @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") - @ApiResponse(responseCode = "201", description = "주문 생성") - public ResponseEntity createOrder( - @PathVariable Long storeId, - @PathVariable Long tableId, - @RequestBody @Valid OrderCreateRequestDto orderCreateRequestDto, - HttpSession session - ) { - String sessionId = session.getId(); - OrderCreateResponseDto response = orderService.createOrder(storeId,tableId,orderCreateRequestDto,sessionId); - return ResponseEntity - .status(HttpStatus.CREATED) - .body( - ApiUtils.success(response) - ); - } - - @GetMapping("/items/{storeId}/{tableId}") - @Operation(summary = "테이블별 주문 아이템 조회", description = "비로그인(세션) 기준으로 테이블의 내 주문 목록만 조회") - @ApiResponse(responseCode = "200", description = "주문 조회") - public ResponseEntity getOrderItems( - @PathVariable Long storeId, - @PathVariable Long tableId, - HttpSession session - ) { - // 세션ID 추출 (Spring이 세션 자동 관리) - String sessionId = session.getId(); - - List orderItems = orderService.getOrderItems(storeId, tableId, sessionId); - return ResponseEntity. - status(HttpStatus.OK) - .body( - ApiUtils.success(orderItems) - ); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java deleted file mode 100644 index 1b8fc52b..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/dto/CartItemDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.apiuser.order.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class CartItemDto { - private final Long menuId; // 메뉴 ID - private final int quantity; // 수량 -} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java deleted file mode 100644 index 056d44a6..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateRequestDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.apiuser.order.dto; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class OrderCreateRequestDto { - private final List items; // 장바구니 항목 리스트 - -} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java deleted file mode 100644 index 1293ac0f..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderCreateResponseDto.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.apiuser.order.dto; - -import java.util.List; - -import com.nowait.domainorder.entity.UserOrder; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class OrderCreateResponseDto { - private Long orderId; // 주문 ID // 주문 상태 (예: "주문완료", "배송중" 등) - private Long storeId; // 상점 ID - private String storeName; // 상점 이름 - private List orderItems; // 주문 항목 목록 - - public static OrderCreateResponseDto fromEntity(UserOrder order) { - return OrderCreateResponseDto.builder() - .orderId(order.getId()) - .storeId(order.getStore().getStoreId()) - .storeName(order.getStore().getName()) - .orderItems(List.of()) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java deleted file mode 100644 index 02e24e3f..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemListGetResponseDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.apiuser.order.dto; - -import com.nowait.domainorder.entity.OrderItem; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class OrderItemListGetResponseDto { - private Long orderId; - private String menuName; - private Integer quantity; - private Integer price; - - public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem) { - return OrderItemListGetResponseDto.builder() - .orderId(orderItem.getUserOrder().getId()) - .menuName(orderItem.getMenu().getName()) - .quantity(orderItem.getQuantity()) - .price(orderItem.getMenu().getPrice()) - .build(); - - } -} diff --git a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java b/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java deleted file mode 100644 index 12f04514..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/dto/OrderItemResponseDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.apiuser.order.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class OrderItemResponseDTO { - private Long menuId; // 메뉴 ID - private String menuName; // 메뉴 이름 - private int quantity; // 수량 -} diff --git a/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java b/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java deleted file mode 100644 index 1644ba16..00000000 --- a/api-user/src/main/java/com/example/apiuser/order/service/OrderService.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.example.apiuser.order.service; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.DigestUtils; - -import com.example.apiuser.order.dto.CartItemDto; -import com.example.apiuser.order.dto.OrderCreateRequestDto; -import com.example.apiuser.order.dto.OrderCreateResponseDto; -import com.example.apiuser.order.dto.OrderItemListGetResponseDto; -import com.example.domainstore.entity.Store; -import com.example.domainstore.repository.StoreRepository; -import com.example.menu.entity.Menu; -import com.example.menu.repository.MenuRepository; -import com.nowait.domainorder.entity.OrderItem; -import com.nowait.domainorder.entity.UserOrder; -import com.nowait.domainorder.exception.DuplicateOrderException; -import com.nowait.domainorder.exception.OrderItemsEmptyException; -import com.nowait.domainorder.exception.OrderParameterEmptyException; -import com.nowait.domainorder.repository.OrderItemRepository; -import com.nowait.domainorder.repository.OrderRepository; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class OrderService { - private final OrderRepository orderRepository; - private final StoreRepository storeRepository; - private final MenuRepository menuRepository; - private final OrderItemRepository orderItemRepository; - @Transactional - public OrderCreateResponseDto createOrder(Long storeId, Long tableId, - OrderCreateRequestDto orderCreateRequestDto, String sessionId) { - parameterValidation(storeId, tableId, orderCreateRequestDto); - - // 💡 [중복 주문 방지] signature 생성 및 체크 - String signature = generateOrderSignature(storeId, tableId, orderCreateRequestDto.getItems()); - checkDuplicateOrderSignature(signature); - - // 1. Store 조회 - Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("store not found")); - - // 2. UserOrder 생성 및 signature 저장 - UserOrder order = UserOrder.builder() - .tableId(tableId) - .store(store) - .signature(signature) // signature 저장 - .sessionId(sessionId) // sessionId 저장 - .build(); - UserOrder savedOrder = orderRepository.save(order); - - // 3. 메뉴 ID 리스트 수집 -> Map으로 캐싱 - List menuIds = orderCreateRequestDto.getItems().stream() - .map(CartItemDto::getMenuId) - .toList(); - - List menus = menuRepository.findAllById(menuIds); - Map menuMap = menus.stream() - .collect(Collectors.toMap(Menu::getId, Function.identity())); - - // 4. 각 장바구니 항목에 대해 OrderItem 생성 및 저장 - List orderItems = orderCreateRequestDto.getItems().stream() - .map(item -> { - Menu menu = Optional.ofNullable(menuMap.get(item.getMenuId())) - .orElseThrow(() -> new IllegalArgumentException("menu not found: " + item.getMenuId())); - return OrderItem.builder() - .userOrder(savedOrder) - .menu(menu) - .quantity(item.getQuantity()) - .build(); - }) - .collect(Collectors.toList()); - - - orderItemRepository.saveAll(orderItems); - - // 5. 응답 반환 - return OrderCreateResponseDto.fromEntity(savedOrder); - } - - @Transactional(readOnly = true) - public List getOrderItems(Long storeId, Long tableId, String sessionId) { - // 1. UserOrder 목록 조회 (storeId, tableId, sessionId 기준) - List userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId); - - // 2. OrderItem으로 변환 - return userOrders.stream() - .flatMap(order -> order.getOrderItems().stream()) - .map(OrderItemListGetResponseDto::fromEntity) - .toList(); - } - - - private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { - if (storeId == null || tableId == null || orderCreateRequestDto == null) { - throw new OrderParameterEmptyException(); - } - if (orderCreateRequestDto.getItems() == null || orderCreateRequestDto.getItems().isEmpty()) { - throw new OrderItemsEmptyException(); - } - } - private String generateOrderSignature(Long storeId, Long tableId, List items) { - String cartString = items.stream() - .sorted((a, b) -> a.getMenuId().compareTo(b.getMenuId())) // 메뉴 ID 기준 정렬 - .map(item -> item.getMenuId() + ":" + item.getQuantity()) - .collect(Collectors.joining(",")); - String raw = storeId + "-" + tableId + "-" + cartString; - return DigestUtils.md5DigestAsHex(raw.getBytes()); - } - - private void checkDuplicateOrderSignature(String signature) { - // 최근 2초 이내 동일 signature 주문이 있는지 검사 - LocalDateTime threshold = LocalDateTime.now().minusSeconds(2); - boolean exists = orderRepository.existsBySignatureAndCreatedAtAfter(signature, threshold); - if (exists) { - throw new DuplicateOrderException(); - } - } -} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java b/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java deleted file mode 100644 index 75416513..00000000 --- a/api-user/src/main/java/com/example/apiuser/reservation/controller/ReservationController.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.example.apiuser.reservation.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.apiuser.reservation.dto.ReservationCreateRequestDto; -import com.example.apiuser.reservation.dto.ReservationCreateResponseDto; -import com.example.apiuser.reservation.service.ReservationService; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowaiting.common.api.ApiUtils; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; - -@Tag(name = "Reservation API", description = "예약 API") -@RestController -@RequestMapping("/reservations") -@RequiredArgsConstructor -public class ReservationController { - - private final ReservationService reservationService; - - @PostMapping("/create/{storeId}") - @Operation(summary = "예약 생성", description = "특정 주점에 대한 예약하기 생성") - @ApiResponse(responseCode = "201", description = "예약 생성") - public ResponseEntity create( - @PathVariable Long storeId, - @AuthenticationPrincipal CustomOAuth2User customOAuth2User, - @RequestBody ReservationCreateRequestDto requestDto) { - ReservationCreateResponseDto response = reservationService.create(storeId, customOAuth2User, requestDto); - return ResponseEntity - .status(HttpStatus.CREATED) - .body( - ApiUtils.success( - response - ) - ); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java b/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java deleted file mode 100644 index 03fbfb4f..00000000 --- a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateRequestDto.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.apiuser.reservation.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ReservationCreateRequestDto { - private Integer partySize; -} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java b/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java deleted file mode 100644 index fa581589..00000000 --- a/api-user/src/main/java/com/example/apiuser/reservation/dto/ReservationCreateResponseDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.apiuser.reservation.dto; - -import java.time.LocalDateTime; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ReservationCreateResponseDto { - private Long id; - private Long storeId; - private Long userId; - private LocalDateTime requestedAt; - private String status; - private Integer partySize; -} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java b/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java deleted file mode 100644 index 37dc86eb..00000000 --- a/api-user/src/main/java/com/example/apiuser/reservation/exception/ReservationNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.apiuser.reservation.exception; - -import com.nowaiting.common.exception.ErrorMessage; - -public class ReservationNotFoundException extends RuntimeException { - public ReservationNotFoundException() { - super(ErrorMessage.NOTFOUND_RESERVATION.getMessage()); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java b/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java deleted file mode 100644 index a5394ec8..00000000 --- a/api-user/src/main/java/com/example/apiuser/reservation/service/ReservationService.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.example.apiuser.reservation.service; - -import java.time.LocalDateTime; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.example.apiuser.reservation.dto.ReservationCreateRequestDto; -import com.example.apiuser.reservation.dto.ReservationCreateResponseDto; -import com.example.domainstore.entity.Store; -import com.example.domainstore.repository.StoreRepository; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.domainreservation.entity.Reservation; -import com.nowait.domainreservation.repository.ReservationRepository; -import com.nowaiting.common.enums.ReservationStatus; -import com.nowaiting.user.entity.User; -import com.nowaiting.user.exception.UserNotFoundException; -import com.nowaiting.user.repository.UserRepository; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class ReservationService { - - private final ReservationRepository reservationRepository; - private final StoreRepository storeRepository; - private final UserRepository userRepository; - - @Transactional - public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User, - ReservationCreateRequestDto requestDto) { - Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 store")); - User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(UserNotFoundException::new); - - Reservation reservation = Reservation.builder() - .store(store) - .user(user) - .requestedAt(LocalDateTime.now()) - .status(ReservationStatus.WAITING) - .partySize(requestDto.getPartySize()) - .build(); - - Reservation saved = reservationRepository.save(reservation); - - return ReservationCreateResponseDto.builder() - .id(saved.getId()) - .storeId(saved.getStore().getStoreId()) - .userId(saved.getUser().getId()) - .requestedAt(saved.getRequestedAt()) - .status(saved.getStatus().name()) - .partySize(saved.getPartySize()) - .build(); - } -} - diff --git a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java b/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java deleted file mode 100644 index 510b19d7..00000000 --- a/api-user/src/main/java/com/example/apiuser/store/controller/StoreController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.example.apiuser.store.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import com.example.apiuser.store.service.StoreService; -import com.nowaiting.common.api.ApiUtils; - -import lombok.RequiredArgsConstructor; - -@RestController -@RequestMapping("v1/stores") -@RequiredArgsConstructor -public class StoreController { - - private final StoreService storeService; - - - @GetMapping("/all-stores") - public ResponseEntity getAllStores() { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - storeService.getAllStores() - ) - ); - } - - @GetMapping("/{storeId}") - public ResponseEntity getStoreById(@PathVariable Long storeId) { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - storeService.getStoreByStoreId(storeId) - ) - ); - } - - @GetMapping("/search") - public ResponseEntity searchStores(@RequestParam("name") String name) { - return ResponseEntity - .ok() - .body( - ApiUtils.success( - storeService.searchStoresByName(name) - ) - ); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java deleted file mode 100644 index 40cb984d..00000000 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreImageUploadResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.apiuser.store.dto; - -import com.example.domainstore.entity.StoreImage; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class StoreImageUploadResponse { - private final Long id; - private final String imageUrl; - private final String type; - - public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { - return StoreImageUploadResponse.builder() - .id(storeImage.getId()) - .imageUrl(storeImage.getImageUrl()) - .type(storeImage.getType()) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java deleted file mode 100644 index d6cfab6e..00000000 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadDto.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.apiuser.store.dto; - -import java.time.LocalDateTime; -import java.util.List; - -import com.example.domainstore.entity.Store; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class StoreReadDto { - private Long storeId; - private Long departmentId; - private String name; - private String location; - private String description; - private List images; - private Boolean isActive; - private Boolean deleted; - private LocalDateTime createdAt; - - public static StoreReadDto fromEntity(Store store, List images) { - return StoreReadDto.builder() - .createdAt(store.getCreatedAt()) - .storeId(store.getStoreId()) - .departmentId(store.getDepartmentId()) - .name(store.getName()) - .location(store.getLocation()) - .description(store.getDescription()) - .isActive(store.getIsActive()) - .deleted(store.getDeleted()) - .images(images) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java b/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java deleted file mode 100644 index a580e562..00000000 --- a/api-user/src/main/java/com/example/apiuser/store/dto/StoreReadResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.apiuser.store.dto; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@Builder -public class StoreReadResponse { - - private List storeReadDtos; - private boolean hasNext; - - public static StoreReadResponse of(List storeReadDtos, boolean hasNext) { - return StoreReadResponse.builder() - .storeReadDtos(storeReadDtos) - .hasNext(hasNext) - .build(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java b/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java deleted file mode 100644 index fd90de2a..00000000 --- a/api-user/src/main/java/com/example/apiuser/store/service/StoreService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.apiuser.store.service; - -import java.util.List; - -import com.example.apiuser.store.dto.StoreReadDto; -import com.example.apiuser.store.dto.StoreReadResponse; - -public interface StoreService { - - StoreReadResponse getAllStores(); - - StoreReadDto getStoreByStoreId(Long storeId); - - List searchStoresByName(String name); - -} diff --git a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java b/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java deleted file mode 100644 index d641f03e..00000000 --- a/api-user/src/main/java/com/example/apiuser/store/service/StoreServiceImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.example.apiuser.store.service; - -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import com.example.apiuser.store.dto.StoreImageUploadResponse; -import com.example.apiuser.store.dto.StoreReadDto; -import com.example.apiuser.store.dto.StoreReadResponse; -import com.example.domainstore.entity.Store; -import com.example.domainstore.entity.StoreImage; -import com.example.domainstore.repository.StoreImageRepository; -import com.example.domainstore.repository.StoreRepository; - -import jakarta.persistence.EntityNotFoundException; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class StoreServiceImpl implements StoreService { - - private final StoreRepository storeRepository; - private final StoreImageRepository storeImageRepository; - - - @Override - @Transactional(readOnly = true) - public StoreReadResponse getAllStores() { - List stores = storeRepository.findAllByDeletedFalse(); - - List storeRead = stores.stream() - .map(store -> { - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - return StoreReadDto.fromEntity(store, imageDto); - }) - .toList(); - - boolean hasNext = false; - - return StoreReadResponse.of(storeRead, hasNext); - } - - @Override - @Transactional(readOnly = true) - public StoreReadDto getStoreByStoreId(Long storeId) { - Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); - - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - - return StoreReadDto.fromEntity(store, imageDto); - } - - @Override - public List searchStoresByName(String name) { - List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(name); - return stores.stream() - .map(store -> { - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - return StoreReadDto.fromEntity(store, imageDto); - }) - .toList(); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java b/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java deleted file mode 100644 index 67bafe53..00000000 --- a/api-user/src/main/java/com/example/apiuser/token/controller/TokenController.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.example.apiuser.token.controller; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.apiuser.token.dto.AuthenticationResponse; -import com.example.apiuser.token.dto.RefreshTokenRequest; -import com.example.apiuser.token.service.TokenService; -import com.nowait.auth.jwt.JwtUtil; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/refresh-token") -@Slf4j -public class TokenController { - private final JwtUtil jwtUtil; - private final TokenService tokenService; - @Value("${jwt.access-token-expiration-ms}") - private long accessTokenExpiration; - @Value("${jwt.refresh-token-expiration-ms}") - private long refreshTokenExpiration; - @PostMapping - public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ - String refreshToken = request.getRefreshToken(); - - // 리프레시 토큰 검증 - Long userId = jwtUtil.getUserId(refreshToken); - String role = jwtUtil.getRole(refreshToken); - - long currentAccessTokenExpiration = accessTokenExpiration; - if (role.equals("SUPER_ADMIN")) { - currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일 - } - - // 리프레시 토큰 유효성 검증 - if (tokenService.validateToken(refreshToken, userId)){ - // 유효한 토큰이라면, 새로운 accessToken, refreshToken 생성 - String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, currentAccessTokenExpiration); - String newRefreshToken = jwtUtil.createRefreshToken("refreshToken", userId, refreshTokenExpiration); - - // DB에 새로운 refreshToken으로 교체 - tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); - - AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, refreshToken); - return ResponseEntity.ok().body(authenticationResponse); - - } - - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); - } -} diff --git a/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java b/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java deleted file mode 100644 index 71db8914..00000000 --- a/api-user/src/main/java/com/example/apiuser/token/dto/AuthenticationResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.apiuser.token.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -@RequiredArgsConstructor -@Getter -@ToString(exclude = {"accessToken", "refreshToken"}) // 로깅 시 토큰 노출 방지 -public class AuthenticationResponse { - @JsonProperty("access_token") - private final String accessToken; - - @JsonProperty("refresh_token") - private final String refreshToken; -} diff --git a/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java b/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java deleted file mode 100644 index 4ab8ae75..00000000 --- a/api-user/src/main/java/com/example/apiuser/token/dto/RefreshTokenRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.apiuser.token.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class RefreshTokenRequest { - @NotBlank(message = "Refresh token은 필수입니다.") - private String refreshToken; -} diff --git a/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java b/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java deleted file mode 100644 index 56845238..00000000 --- a/api-user/src/main/java/com/example/apiuser/token/service/TokenService.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.example.apiuser.token.service; - -import java.time.LocalDateTime; -import java.util.Optional; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.example.domaintoken.entity.Token; -import com.example.domaintoken.repository.TokenRepository; -import com.nowait.auth.jwt.JwtUtil; -import com.nowait.exception.RefreshTokenNotFoundException; -import com.nowait.exception.TokenBadRequestException; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Service -@RequiredArgsConstructor -@Slf4j -public class TokenService { - private final TokenRepository tokenRepository; - private final JwtUtil jwtUtil; - - @Transactional - public Boolean validateToken(String token, Long userId){ - // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. - Optional savedToken = tokenRepository.findByUserId(userId); - - // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 - if (savedToken.isEmpty()){ - log.info("여기에 걸렸니 ? -- 1 "); - return false; - } - - // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 - if (!savedToken.get().getRefreshToken().equals(token)){ - log.info("여기에 걸렸니 ? -- 2 "); - return false; - } - - // 리프레시 토큰의 만료여부 확인 - if(jwtUtil.isExpired(token)){ - log.info("여기에 걸렸니 ? -- 3 "); - return false; // 만료된 토큰은 유효하지 않음 - } - - log.info("여기에 걸렸니 ? -- 4 "); - return true; // 모든 조건 만족 시, 유효한 토큰 - } - - @Transactional - public void updateRefreshToken(Long userId, String oldRefreshToken, String newRefreshToken){ - Token token = tokenRepository.findByUserId(userId) - .orElseThrow(RefreshTokenNotFoundException::new); // 404 - - if (!token.getRefreshToken().equals(oldRefreshToken)){ - throw new TokenBadRequestException(); // 400 - } - - // 기존 토큰 삭제 및 새 토큰 저장 - tokenRepository.delete(token); - Token newToken = Token.toEntity(token.getUser(), newRefreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(newToken); - } -} diff --git a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java index 50c6795f..89de6631 100644 --- a/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java +++ b/application-user/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java @@ -3,6 +3,9 @@ import java.util.List; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; @@ -15,9 +18,10 @@ public class OrderCreateRequestDto { @NotBlank(message = "주문자 이름은 필수입니다") @Size(max = 10, message = "주문자 이름은 10자 이하여야 합니다") private final String depositorName; // 예약자 이름 - @NotBlank(message = "주문 내역은 필수입니다") + @NotEmpty(message = "주문 내역은 필수입니다") private final List items; // 장바구니 항목 리스트 - @NotBlank(message = "주문금액은 필수 입니다.") + @NotNull(message = "주문금액은 필수입니다") + @Positive(message = "주문금액은 양수여야 합니다") private final int totalPrice; } diff --git a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java index a1c27498..c5e4639a 100644 --- a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java +++ b/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java @@ -56,6 +56,7 @@ public class UserOrder extends BaseTimeEntity { @Enumerated(value = EnumType.STRING) private OrderStatus status = OrderStatus.WAITING_FOR_PAYMENT; + @Column(nullable = false) private Integer totalPrice; public void updateStatus(OrderStatus newStatus) { From b9bb8cb6ffa3f5bae99e5afb2ecf958b9f7cc7aa Mon Sep 17 00:00:00 2001 From: Harry <114489245+HyemIin@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:24:48 +0900 Subject: [PATCH 086/685] =?UTF-8?q?Update=20and=20rename=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C-=EC=9D=B4=EC=8A=88.md=20to=20needdelete.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/needdelete.md | 1 + ...27\224\353\223\234-\354\235\264\354\212\210.md" | 14 -------------- 2 files changed, 1 insertion(+), 14 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/needdelete.md delete mode 100644 ".github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" diff --git a/.github/ISSUE_TEMPLATE/needdelete.md b/.github/ISSUE_TEMPLATE/needdelete.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/needdelete.md @@ -0,0 +1 @@ + diff --git "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" deleted file mode 100644 index f728915f..00000000 --- "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: 백엔드 이슈 -about: 백엔드와 관련된 이슈 -title: "[백엔드] " -labels: Backend -assignees: '' - ---- - -# 이슈 내용 - - -# 작업 목록 -- [ ] From 724034205dd92ba25b54feebde1f74173eccc9aa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 19:35:15 +0900 Subject: [PATCH 087/685] =?UTF-8?q?refactor:=20=EB=A9=80=ED=8B=B0=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/ApiAdminApplication.java | 37 ----------- application-config/build.gradle | 14 ---- application-user/build.gradle | 59 ----------------- .../applicationuser/ApiUserApplication.java | 40 ------------ build.gradle | 46 ++++--------- common/build.gradle | 10 --- domain-base/.gitattributes | 3 - domain-base/build.gradle | 29 --------- domain-bookmark/build.gradle | 32 --------- domain-menu/.gitattributes | 3 - domain-menu/gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 -- domain-reservation/.gitignore | 37 ----------- domain-reservation/build.gradle | 32 --------- domain-store/.gitattributes | 3 - domain-store/.gitignore | 37 ----------- domain-store/build.gradle | 30 --------- .../gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 -- domain-token/.gitattributes | 3 - domain-token/.gitignore | 37 ----------- domain-token/build.gradle | 31 --------- .../src/main/resources/application.properties | 1 - domain-user/.gitattributes | 3 - domain-user/.gitignore | 37 ----------- external-oauth/.gitignore | 37 ----------- external-oauth/build.gradle | 40 ------------ infra-aws/.gitattributes | 3 - infra-aws/.gitignore | 37 ----------- .../.gitattributes | 0 .../.gitignore | 0 .../build.gradle | 36 +++++------ .../java/com/nowait/ApiAdminApplication.java | 13 ++++ .../config/SwaggerConfig.java | 29 +++++++++ .../config/security/CorsConfig.java | 28 ++++++++ .../config/security}/SecurityConfig.java | 8 +-- .../exception/GlobalExceptionHandler.java | 16 ++--- .../menu/controller/MenuController.java | 0 .../menu/controller/MenuImageController.java | 0 .../menu/dto/MenuCreateRequest.java | 2 +- .../menu/dto/MenuCreateResponse.java | 2 +- .../menu/dto/MenuImageUploadResponse.java | 2 +- .../menu/dto/MenuReadDto.java | 2 +- .../menu/dto/MenuReadResponse.java | 0 .../menu/dto/MenuUpdateRequest.java | 0 .../menu/service/MenuImageService.java | 16 ++--- .../menu/service/MenuService.java | 12 ++-- .../order/controller/OrderController.java | 2 +- .../controller/ReservationController.java | 0 .../reservation/dto/CallGetResponseDto.java | 2 +- .../dto/ReservationGetResponseDto.java | 2 +- .../dto/ReservationStatusSummaryDto.java | 0 .../ReservationStatusUpdateRequestDto.java | 0 .../service/ReservationService.java | 6 +- .../RefreshTokenNotFoundException.java | 2 +- .../exception/ResourceNotFoundException.java | 2 +- .../exception/TokenBadRequestException.java | 4 +- .../exception/UnauthorizedException.java | 2 +- .../security}/jwt/JwtAuthorizationFilter.java | 4 +- .../security}/jwt/JwtUtil.java | 2 +- .../service/CustomUserDetailService.java | 8 +-- .../store/controller/StoreController.java | 0 .../controller/StoreImageController.java | 0 .../store/dto/StoreCreateRequest.java | 2 +- .../store/dto/StoreCreateResponse.java | 2 +- .../store/dto/StoreImageUploadResponse.java | 2 +- .../store/dto/StoreReadDto.java | 2 +- .../store/dto/StoreUpdateRequest.java | 0 .../store/service/StoreImageService.java | 17 +++-- .../store/service/StoreService.java | 0 .../store/service/StoreServiceImpl.java | 13 ++-- .../token/controller/TokenController.java | 2 +- .../token/dto/AuthenticationResponse.java | 0 .../token/dto/RefreshTokenRequest.java | 0 .../token/service/TokenService.java | 10 +-- .../user/controller/UserController.java | 0 .../user/dto/ManagerLoginRequestDto.java | 0 .../user/dto/ManagerLoginResponseDto.java | 2 +- .../user/dto/ManagerSignupRequestDto.java | 2 +- .../user/dto/ManagerSignupResponseDto.java | 2 +- .../user/dto/UserResponseDto.java | 0 .../user/serivce/UserService.java | 8 +-- .../.gitattributes | 0 .../.gitignore | 0 nowait-app-user-api/build.gradle | 61 ++++++++++++++++++ .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../java/com/nowait/ApiUserApplication.java | 15 +++++ .../controller/BookmarkController.java | 2 +- .../bookmark/dto/BookmarkCreateResponse.java | 2 +- .../bookmark/dto/BookmarkGetResponse.java | 2 +- .../bookmark/service/BookmarkService.java | 14 ++-- .../applicationuser/config/SwaggerConfig.java | 29 +++++++++ .../config/security}/CorsConfig.java | 2 +- .../config/security}/SecurityConfig.java | 12 ++-- .../exception/GlobalExceptionHandler.java | 23 +++---- .../menu/controller/MenuController.java | 0 .../menu/dto/MenuImageUploadResponse.java | 2 +- .../applicationuser/menu/dto/MenuReadDto.java | 2 +- .../menu/dto/MenuReadResponse.java | 0 .../menu/service/MenuService.java | 12 ++-- .../oauth}/dto/KaKaoResponse.java | 2 +- .../oauth}/dto/OAuth2Response.java | 2 +- .../oauth2/CustomOAuth2UserService.java | 12 ++-- .../oauth2/OAuth2LoginSuccessHandler.java | 13 ++-- .../order/controller/OrderController.java | 0 .../order/dto/CartItemDto.java | 0 .../order/dto/OrderCreateRequestDto.java | 0 .../order/dto/OrderCreateResponseDto.java | 2 +- .../dto/OrderItemListGetResponseDto.java | 3 +- .../order/dto/OrderItemResponseDTO.java | 0 .../order/service/OrderService.java | 22 +++---- .../controller/ReservationController.java | 2 +- .../dto/ReservationCreateRequestDto.java | 0 .../dto/ReservationCreateResponseDto.java | 0 .../service/ReservationService.java | 16 ++--- .../RefreshTokenNotFoundException.java | 2 +- .../exception/ResourceNotFoundException.java | 2 +- .../exception/TokenBadRequestException.java | 4 +- .../exception/UnauthorizedException.java | 2 +- .../security}/jwt/JwtAuthorizationFilter.java | 7 +- .../security}/jwt/JwtUtil.java | 2 +- .../service/CustomUserDetailService.java | 8 +-- .../store/controller/StoreController.java | 0 .../store/dto/StoreImageUploadResponse.java | 2 +- .../store/dto/StoreReadDto.java | 2 +- .../store/dto/StoreReadResponse.java | 0 .../store/service/StoreService.java | 0 .../store/service/StoreServiceImpl.java | 16 ++--- .../token/controller/TokenController.java | 2 +- .../token/dto/AuthenticationResponse.java | 0 .../token/dto/RefreshTokenRequest.java | 0 .../token/service/TokenService.java | 11 ++-- .../.gitattributes | 0 .../.gitignore | 0 nowait-common/build.gradle | 15 +++++ .../java/com/nowait/common/api/ApiError.java | 0 .../java/com/nowait/common/api/ApiResult.java | 0 .../java/com/nowait/common/api/ApiUtils.java | 0 .../nowait/common/enums/HttpStatusCode.java | 0 .../common/enums/ReservationStatus.java | 0 .../java/com/nowait/common/enums/Role.java | 3 +- .../com/nowait/common/enums/SocialType.java | 0 .../nowait/common/exception/ErrorMessage.java | 0 .../common/exception/ErrorResponse.java | 0 {common => nowait-domain}/.gitignore | 0 {domain-user => nowait-domain}/build.gradle | 13 +++- .../domain-admin-rdb}/.gitignore | 0 .../domain-admin-rdb}/build.gradle | 12 +++- .../domain-core-rdb}/.gitignore | 0 nowait-domain/domain-core-rdb/build.gradle | 47 ++++++++++++++ .../base/entity}/BaseTimeEntity.java | 2 +- .../domaincorerdb}/menu/entity/Menu.java | 5 +- .../domaincorerdb}/menu/entity/MenuImage.java | 4 +- .../exception/MenuImageEmptyException.java | 2 +- .../exception/MenuImageNotFoundException.java | 2 +- .../menu/exception/MenuNotFoundException.java | 2 +- .../exception/MenuParamEmptyException.java | 2 +- .../menu/repository/MenuImageRepository.java | 6 +- .../menu/repository/MenuRepository.java | 4 +- .../order/entity/OrderItem.java | 6 +- .../order/entity/UserOrder.java | 6 +- .../DepositorNameTooLongException.java | 2 +- .../exception/DuplicateOrderException.java | 2 +- .../exception/OrderItemsEmptyException.java | 2 +- .../OrderParameterEmptyException.java | 2 +- .../order/repository/OrderItemRepository.java | 4 +- .../order/repository/OrderRepository.java | 5 +- .../reservation/entity/Reservation.java | 6 +- .../ReservationNotFoundException.java | 2 +- .../repository/ReservationRepository.java | 6 +- .../domaincorerdb}/store/entity/Store.java | 5 +- .../store/entity/StoreImage.java | 4 +- .../exception/StoreImageEmptyException.java | 2 +- .../StoreImageNotFoundException.java | 2 +- .../exception/StoreKeywordEmptyException.java | 2 +- .../exception/StoreNotFoundException.java | 2 +- .../exception/StoreParamEmptyException.java | 2 +- .../repository/StoreImageRepository.java | 6 +- .../store/repository/StoreRepository.java | 5 +- .../domaincorerdb}/token/entity/Token.java | 7 +- .../token/exception/BusinessException.java | 2 +- .../token/repository/TokenRepository.java | 6 +- .../user/entity/MemberDetails.java | 2 +- .../domaincorerdb}/user/entity/User.java | 2 +- .../user/exception/UserNotFoundException.java | 2 +- .../user/repository/UserRepository.java | 6 +- .../domain-user-rdb}/.gitignore | 0 .../domain-user-rdb}/build.gradle | 19 ++++-- .../bookmark/entity/Bookmark.java | 8 +-- .../BookmarkOwnerMismatchException.java | 2 +- .../exception/DuplicateBookmarkException.java | 2 +- .../repository/BookmarkRepository.java | 8 +-- .../oauth}/dto/CustomOAuth2User.java | 4 +- {common => nowait-infra}/.gitattributes | 0 {domain-order => nowait-infra}/.gitignore | 0 {infra-aws => nowait-infra}/build.gradle | 12 +++- .../nowait/infraaws/aws}/s3/S3Service.java | 2 +- .../nowait/infraaws}/config/AsyncConfig.java | 2 +- .../nowait/infraaws/config/AwsS3Config.java | 0 security-admin/.gitattributes | 3 - security-admin/.gitignore | 37 ----------- security-admin/build.gradle | 38 ----------- security-front/.gitignore | 37 ----------- security-front/build.gradle | 41 ------------ settings.gradle | 24 ++----- 206 files changed, 585 insertions(+), 1106 deletions(-) delete mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java delete mode 100644 application-config/build.gradle delete mode 100644 application-user/build.gradle delete mode 100644 application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java delete mode 100644 common/build.gradle delete mode 100644 domain-base/.gitattributes delete mode 100644 domain-base/build.gradle delete mode 100644 domain-bookmark/build.gradle delete mode 100644 domain-menu/.gitattributes delete mode 100644 domain-menu/gradle/wrapper/gradle-wrapper.jar delete mode 100644 domain-menu/gradle/wrapper/gradle-wrapper.properties delete mode 100644 domain-reservation/.gitignore delete mode 100644 domain-reservation/build.gradle delete mode 100644 domain-store/.gitattributes delete mode 100644 domain-store/.gitignore delete mode 100644 domain-store/build.gradle delete mode 100644 domain-store/gradle/wrapper/gradle-wrapper.jar delete mode 100644 domain-store/gradle/wrapper/gradle-wrapper.properties delete mode 100644 domain-token/.gitattributes delete mode 100644 domain-token/.gitignore delete mode 100644 domain-token/build.gradle delete mode 100644 domain-token/src/main/resources/application.properties delete mode 100644 domain-user/.gitattributes delete mode 100644 domain-user/.gitignore delete mode 100644 external-oauth/.gitignore delete mode 100644 external-oauth/build.gradle delete mode 100644 infra-aws/.gitattributes delete mode 100644 infra-aws/.gitignore rename {application-admin => nowait-app-admin-api}/.gitattributes (100%) rename {application-admin => nowait-app-admin-api}/.gitignore (100%) rename {application-admin => nowait-app-admin-api}/build.gradle (57%) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/SwaggerConfig.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java rename {security-admin/src/main/java/com/nowait/adminsecurity/auth/config => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security}/SecurityConfig.java (91%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (90%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java (92%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java (94%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/dto/MenuImageUploadResponse.java (88%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java (94%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadResponse.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java (74%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (89%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java (92%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java (91%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java (92%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusSummaryDto.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusUpdateRequestDto.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (90%) rename {security-admin/src/main/java/com/nowait/adminsecurity => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/exception/RefreshTokenNotFoundException.java (80%) rename {security-front/src/main/java/com/nowait/frontsecurity => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/exception/ResourceNotFoundException.java (87%) rename {security-admin/src/main/java/com/nowait/adminsecurity => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/exception/TokenBadRequestException.java (63%) rename {security-admin/src/main/java/com/nowait/adminsecurity => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/exception/UnauthorizedException.java (89%) rename {security-admin/src/main/java/com/nowait/adminsecurity/auth => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/jwt/JwtAuthorizationFilter.java (94%) rename {security-admin/src/main/java/com/nowait/adminsecurity/auth => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/jwt/JwtUtil.java (97%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security}/service/CustomUserDetailService.java (78%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java (92%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java (94%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java (87%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java (94%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/dto/StoreUpdateRequest.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java (81%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (88%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java (97%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/token/dto/AuthenticationResponse.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/token/dto/RefreshTokenRequest.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java (86%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/controller/UserController.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginRequestDto.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java (95%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java (96%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupResponseDto.java (94%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/dto/UserResponseDto.java (100%) rename {application-admin => nowait-app-admin-api}/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java (92%) rename {application-config => nowait-app-user-api}/.gitattributes (100%) rename {application-user => nowait-app-user-api}/.gitignore (100%) create mode 100644 nowait-app-user-api/build.gradle rename {application-user => nowait-app-user-api}/gradle/wrapper/gradle-wrapper.jar (100%) rename {application-user => nowait-app-user-api}/gradle/wrapper/gradle-wrapper.properties (100%) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java (97%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkCreateResponse.java (90%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/bookmark/dto/BookmarkGetResponse.java (89%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java (86%) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/SwaggerConfig.java rename {application-config/src/main/java/com/nowait/config/config => nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security}/CorsConfig.java (96%) rename {security-front/src/main/java/com/nowait/frontsecurity/auth/config => nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security}/SecurityConfig.java (89%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java (89%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/menu/dto/MenuImageUploadResponse.java (88%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java (94%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java (82%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth}/dto/KaKaoResponse.java (95%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth}/dto/OAuth2Response.java (90%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth}/oauth2/CustomOAuth2UserService.java (86%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth}/oauth2/OAuth2LoginSuccessHandler.java (89%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/dto/CartItemDto.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateRequestDto.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java (94%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java (92%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/order/service/OrderService.java (87%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java (96%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateRequestDto.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/reservation/dto/ReservationCreateResponseDto.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java (76%) rename {security-front/src/main/java/com/nowait/frontsecurity => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/exception/RefreshTokenNotFoundException.java (80%) rename {security-admin/src/main/java/com/nowait/adminsecurity => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/exception/ResourceNotFoundException.java (87%) rename {security-front/src/main/java/com/nowait/frontsecurity => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/exception/TokenBadRequestException.java (63%) rename {security-front/src/main/java/com/nowait/frontsecurity => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/exception/UnauthorizedException.java (89%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/jwt/JwtAuthorizationFilter.java (95%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/jwt/JwtUtil.java (97%) rename {security-admin/src/main/java/com/nowait/adminsecurity/auth => nowait-app-user-api/src/main/java/com/nowait/applicationuser/security}/service/CustomUserDetailService.java (78%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java (87%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/store/dto/StoreReadDto.java (94%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/store/dto/StoreReadResponse.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/store/service/StoreService.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java (88%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java (97%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/token/dto/AuthenticationResponse.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java (100%) rename {application-user => nowait-app-user-api}/src/main/java/com/nowait/applicationuser/token/service/TokenService.java (86%) rename {application-user => nowait-common}/.gitattributes (100%) rename {application-config => nowait-common}/.gitignore (100%) create mode 100644 nowait-common/build.gradle rename {common => nowait-common}/src/main/java/com/nowait/common/api/ApiError.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/api/ApiResult.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/api/ApiUtils.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/enums/HttpStatusCode.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/enums/ReservationStatus.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/enums/Role.java (91%) rename {common => nowait-common}/src/main/java/com/nowait/common/enums/SocialType.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/exception/ErrorMessage.java (100%) rename {common => nowait-common}/src/main/java/com/nowait/common/exception/ErrorResponse.java (100%) rename {common => nowait-domain}/.gitignore (100%) rename {domain-user => nowait-domain}/build.gradle (90%) rename {domain-base => nowait-domain/domain-admin-rdb}/.gitignore (100%) rename {domain-menu => nowait-domain/domain-admin-rdb}/build.gradle (77%) rename {domain-bookmark => nowait-domain/domain-core-rdb}/.gitignore (100%) create mode 100644 nowait-domain/domain-core-rdb/build.gradle rename {domain-base/src/main/java/com/nowait/base => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/base/entity}/BaseTimeEntity.java (95%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/entity/Menu.java (94%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/entity/MenuImage.java (89%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/exception/MenuImageEmptyException.java (81%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/exception/MenuImageNotFoundException.java (81%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/exception/MenuNotFoundException.java (80%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/exception/MenuParamEmptyException.java (81%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/repository/MenuImageRepository.java (63%) rename {domain-menu/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/menu/repository/MenuRepository.java (81%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/entity/OrderItem.java (84%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/entity/UserOrder.java (89%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/exception/DepositorNameTooLongException.java (82%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/exception/DuplicateOrderException.java (80%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/exception/OrderItemsEmptyException.java (80%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/exception/OrderParameterEmptyException.java (81%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/repository/OrderItemRepository.java (65%) rename {domain-order/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/order/repository/OrderRepository.java (80%) rename {domain-reservation/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/reservation/entity/Reservation.java (90%) rename {domain-reservation/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/reservation/exception/ReservationNotFoundException.java (80%) rename {domain-reservation/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/reservation/repository/ReservationRepository.java (57%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/entity/Store.java (94%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/entity/StoreImage.java (89%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/exception/StoreImageEmptyException.java (80%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/exception/StoreImageNotFoundException.java (81%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/exception/StoreKeywordEmptyException.java (81%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/exception/StoreNotFoundException.java (80%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/exception/StoreParamEmptyException.java (81%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/repository/StoreImageRepository.java (63%) rename {domain-store/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/store/repository/StoreRepository.java (85%) rename {domain-token/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/token/entity/Token.java (89%) rename {domain-token/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/token/exception/BusinessException.java (87%) rename {domain-token/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/token/repository/TokenRepository.java (56%) rename {domain-user/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/user/entity/MemberDetails.java (96%) rename {domain-user/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/user/entity/User.java (97%) rename {domain-user/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/user/exception/UserNotFoundException.java (80%) rename {domain-user/src/main/java/com/nowait => nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/user/repository/UserRepository.java (61%) rename {domain-menu => nowait-domain/domain-user-rdb}/.gitignore (100%) rename {domain-order => nowait-domain/domain-user-rdb}/build.gradle (58%) rename {domain-bookmark/src/main/java/com/nowait => nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb}/bookmark/entity/Bookmark.java (84%) rename {domain-bookmark/src/main/java/com/nowait => nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb}/bookmark/exception/BookmarkOwnerMismatchException.java (80%) rename {domain-bookmark/src/main/java/com/nowait => nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb}/bookmark/exception/DuplicateBookmarkException.java (80%) rename {domain-bookmark/src/main/java/com/nowait => nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb}/bookmark/repository/BookmarkRepository.java (61%) rename {external-oauth/src/main/java/com/nowait/externaloauth => nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth}/dto/CustomOAuth2User.java (92%) rename {common => nowait-infra}/.gitattributes (100%) rename {domain-order => nowait-infra}/.gitignore (100%) rename {infra-aws => nowait-infra}/build.gradle (93%) rename {infra-aws/src/main/java/com/nowait/infraaws => nowait-infra/src/main/java/com/nowait/infraaws/aws}/s3/S3Service.java (98%) rename {application-config/src/main/java/com/nowait/config => nowait-infra/src/main/java/com/nowait/infraaws}/config/AsyncConfig.java (94%) rename {infra-aws => nowait-infra}/src/main/java/com/nowait/infraaws/config/AwsS3Config.java (100%) delete mode 100644 security-admin/.gitattributes delete mode 100644 security-admin/.gitignore delete mode 100644 security-admin/build.gradle delete mode 100644 security-front/.gitignore delete mode 100644 security-front/build.gradle diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java b/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java deleted file mode 100644 index 0e8b5c2f..00000000 --- a/application-admin/src/main/java/com/nowait/applicationadmin/ApiAdminApplication.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.nowait.applicationadmin; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@EnableJpaAuditing -@SpringBootApplication(scanBasePackages = { - "com.nowait.applicationadmin", - "com.nowait.infraaws", - "com.nowait.adminsecurity", - "com.nowait.config", - "com.nowait.reservation" -}) -@EntityScan(basePackages = { - "com.nowait.menu.entity", - "com.nowait.store.entity", - "com.nowait.token.entity", - "com.nowait.user.entity", - "com.nowait.reservation.entity", - "com.nowait.order.entity" -}) -@EnableJpaRepositories(basePackages = { - "com.nowait.menu.repository", - "com.nowait.user.repository", - "com.nowait.store.repository", - "com.nowait.token.repository", - "com.nowait.order.repository", - "com.nowait.reservation.repository" -}) -public class ApiAdminApplication { - public static void main(String[] args) { - org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); - } -} - diff --git a/application-config/build.gradle b/application-config/build.gradle deleted file mode 100644 index a6b89a70..00000000 --- a/application-config/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id 'java-library' -} - -group = 'org.example' -version = '0.0.1-SNAPSHOT' - -repositories { - mavenCentral() -} - -dependencies { - api 'org.springframework.boot:spring-boot-starter-web' -} diff --git a/application-user/build.gradle b/application-user/build.gradle deleted file mode 100644 index cfa67b69..00000000 --- a/application-user/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -plugins { - id 'org.springframework.boot' version '3.5.0' - id 'io.spring.dependency-management' version '1.1.7' - id 'java' -} - -group = 'com.nowaiting' -version = '0.0.1-SNAPSHOT' - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':domain-user') // 도메인 계층 의존성 - implementation project(':domain-menu') // 메뉴 관련 도메인 - implementation project(':domain-base') // 시간 관련 도메인 - implementation project(':domain-store') // 주점 관련 도메인 - implementation project(':domain-token') // 토큰 관련 도메인 - implementation project(':domain-bookmark') // 북마크 관련 도메인 - implementation project(':domain-order') // 주문 관련 도메인 - implementation project(':domain-reservation') // 예약 관련 도메인 - implementation project(':infra-aws') // 사용자 관련 도메인 - implementation project(':security-front') // 사용자 관련 도메인 - implementation project(':external-oauth') // 외부 OAuth 관련 도메인 - implementation project(':application-config') - - // Spring Boot Starter - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // SPRING SECURITY - implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - - // DB - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'com.mysql:mysql-connector-j' - - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' - - // 기타 필요 라이브러리 (예: Lombok) - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} - -test { - useJUnitPlatform() -} diff --git a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java b/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java deleted file mode 100644 index fcfb659f..00000000 --- a/application-user/src/main/java/com/nowait/applicationuser/ApiUserApplication.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.nowait.applicationuser; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@EnableJpaAuditing -@SpringBootApplication(scanBasePackages = { - "com.nowait.applicationuser", - "com.nowait.frontsecurity", - "com.nowait.externaloauth", - "com.nowait.config" -}) -@EntityScan(basePackages = { - "com.nowait.menu.entity", - "com.nowait.store.entity", - "com.nowait.token.entity", - "com.nowait.user.entity", - "com.nowait.bookmark.entity", - "com.nowait.reservation.entity", - "com.nowait.order.entity" -}) -@EnableJpaRepositories(basePackages = { - "com.nowait.menu.repository", - "com.nowait.user.repository", - "com.nowait.store.repository", - "com.nowait.token.repository", - "com.nowait.bookmark.repository", - "com.nowait.order.repository", - "com.nowait.reservation.repository" -}) -public class ApiUserApplication { - - public static void main(String[] args) { - SpringApplication.run(ApiUserApplication.class, args); - } - -} diff --git a/build.gradle b/build.gradle index 791934ae..03ccdb50 100644 --- a/build.gradle +++ b/build.gradle @@ -1,46 +1,30 @@ plugins { - id 'org.springframework.boot' version '3.5.0' apply false - id 'io.spring.dependency-management' version '1.1.7' apply false + id 'org.springframework.boot' version '3.5.0' + id 'io.spring.dependency-management' version '1.1.7' } -group = 'com.nowaiting' +group = 'com.nowait' version = '0.0.1-SNAPSHOT' repositories { mavenCentral() } +allprojects { + group = 'com.nowait' + version = '0.0.1-SNAPSHOT' +} + subprojects { - apply plugin: 'java' - apply plugin: 'org.springframework.boot' - apply plugin: 'io.spring.dependency-management' - - group = rootProject.group - version = rootProject.version - - // 실행 모듈만 따로 정의 - def applicationModules = ['application-admin', 'application-user'] - - if (applicationModules.contains(project.name)) { - // 실제 Service 실행 모듈 - apply plugin: 'org.springframework.boot' - bootJar { enabled = true } - jar { enabled = false } - } else { - // 라이브러리/설정/도메인 전용 모듈 - bootJar { enabled = false } - jar { enabled = true } - } + apply plugin: "java" + apply plugin: 'java-library' + apply plugin: "io.spring.dependency-management" + apply plugin: "org.springframework.boot" repositories { mavenCentral() } - java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } - } dependencyManagement { imports { @@ -48,12 +32,6 @@ subprojects { } } - if (project.name != 'common') { - dependencies { - implementation project(':common') - } - } - test { useJUnitPlatform() } diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index 44e9a75f..00000000 --- a/common/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -jar { - enabled = true -} -bootJar { - enabled = false -} - - -dependencies { -} diff --git a/domain-base/.gitattributes b/domain-base/.gitattributes deleted file mode 100644 index 8af972cd..00000000 --- a/domain-base/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -/gradlew text eol=lf -*.bat text eol=crlf -*.jar binary diff --git a/domain-base/build.gradle b/domain-base/build.gradle deleted file mode 100644 index 6ae34bc8..00000000 --- a/domain-base/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - id 'java-library' -} - -group = 'com.nowaiting' -version = rootProject.version - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -repositories { - mavenCentral() -} - -dependencies { - // JPA Auditing 및 @MappedSuperclass 를 위한 - api 'org.springframework.boot:spring-boot-starter-data-jpa' - // JPA API - api 'jakarta.persistence:jakarta.persistence-api:3.1.0' - // Lombok - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - // Jackson - api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' // Jackson 핵심 - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' -} diff --git a/domain-bookmark/build.gradle b/domain-bookmark/build.gradle deleted file mode 100644 index 82ebe024..00000000 --- a/domain-bookmark/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id 'java-library' -} - -group = 'com.nowaiting' -version = rootProject.version - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':common') - implementation project(':domain-base') - implementation project(':domain-user') - implementation project(':domain-store') - - api 'org.springframework.boot:spring-boot-starter-data-jpa' - api 'jakarta.persistence:jakarta.persistence-api:3.1.0' - - compileOnly 'org.projectlombok:lombok:1.18.26' - annotationProcessor 'org.projectlombok:lombok:1.18.26' - - testImplementation 'org.junit.jupiter:junit-jupiter' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' -} diff --git a/domain-menu/.gitattributes b/domain-menu/.gitattributes deleted file mode 100644 index 8af972cd..00000000 --- a/domain-menu/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -/gradlew text eol=lf -*.bat text eol=crlf -*.jar binary diff --git a/domain-menu/gradle/wrapper/gradle-wrapper.jar b/domain-menu/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 1b33c55baabb587c669f562ae36f953de2481846..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43764 zcma&OWmKeVvL#I6?i3D%6z=Zs?ofE*?rw#G$eqJB ziT4y8-Y@s9rkH0Tz>ll(^xkcTl)CY?rS&9VNd66Yc)g^6)JcWaY(5$5gt z8gr3SBXUTN;~cBgz&})qX%#!Fxom2Yau_`&8)+6aSN7YY+pS410rRUU*>J}qL0TnJ zRxt*7QeUqTh8j)Q&iavh<}L+$Jqz))<`IfKussVk%%Ah-Ti?Eo0hQH!rK%K=#EAw0 zwq@@~XNUXRnv8$;zv<6rCRJ6fPD^hfrh;0K?n z=p!u^3xOgWZ%f3+?+>H)9+w^$Tn1e;?UpVMJb!!;f)`6f&4|8mr+g)^@x>_rvnL0< zvD0Hu_N>$(Li7|Jgu0mRh&MV+<}`~Wi*+avM01E)Jtg=)-vViQKax!GeDc!xv$^mL z{#OVBA$U{(Zr8~Xm|cP@odkHC*1R8z6hcLY#N@3E-A8XEvpt066+3t9L_6Zg6j@9Q zj$$%~yO-OS6PUVrM2s)(T4#6=JpI_@Uz+!6=GdyVU?`!F=d;8#ZB@(5g7$A0(`eqY z8_i@3w$0*es5mrSjhW*qzrl!_LQWs4?VfLmo1Sd@Ztt53+etwzAT^8ow_*7Jp`Y|l z*UgSEwvxq+FYO!O*aLf-PinZYne7Ib6ny3u>MjQz=((r3NTEeU4=-i0LBq3H-VJH< z^>1RE3_JwrclUn9vb7HcGUaFRA0QHcnE;6)hnkp%lY1UII#WPAv?-;c?YH}LWB8Nl z{sx-@Z;QxWh9fX8SxLZk8;kMFlGD3Jc^QZVL4nO)1I$zQwvwM&_!kW+LMf&lApv#< zur|EyC|U@5OQuph$TC_ZU`{!vJp`13e9alaR0Dbn5ikLFH7>eIz4QbV|C=%7)F=qo z_>M&5N)d)7G(A%c>}UCrW!Ql_6_A{?R7&CL`;!KOb3 z8Z=$YkV-IF;c7zs{3-WDEFJzuakFbd*4LWd<_kBE8~BFcv}js_2OowRNzWCtCQ6&k z{&~Me92$m*@e0ANcWKuz)?YjB*VoSTx??-3Cc0l2U!X^;Bv@m87eKHukAljrD54R+ zE;@_w4NPe1>3`i5Qy*3^E9x#VB6?}v=~qIprrrd5|DFkg;v5ixo0IsBmik8=Y;zv2 z%Bcf%NE$a44bk^`i4VwDLTbX=q@j9;JWT9JncQ!+Y%2&HHk@1~*L8-{ZpY?(-a9J-1~<1ltr9i~D9`P{XTIFWA6IG8c4;6bFw*lzU-{+?b&%OcIoCiw00n>A1ra zFPE$y@>ebbZlf(sN_iWBzQKDV zmmaLX#zK!@ZdvCANfwV}9@2O&w)!5gSgQzHdk2Q`jG6KD7S+1R5&F)j6QTD^=hq&7 zHUW+r^da^%V(h(wonR(j?BOiC!;y=%nJvz?*aW&5E87qq;2z`EI(f zBJNNSMFF9U{sR-af5{IY&AtoGcoG)Iq-S^v{7+t0>7N(KRoPj;+2N5;9o_nxIGjJ@ z7bYQK)bX)vEhy~VL%N6g^NE@D5VtV+Q8U2%{ji_=6+i^G%xeskEhH>Sqr194PJ$fB zu1y^){?9Vkg(FY2h)3ZHrw0Z<@;(gd_dtF#6y_;Iwi{yX$?asr?0N0_B*CifEi7<6 zq`?OdQjCYbhVcg+7MSgIM|pJRu~`g?g3x?Tl+V}#$It`iD1j+!x+!;wS0+2e>#g?Z z*EA^k7W{jO1r^K~cD#5pamp+o@8&yw6;%b|uiT?{Wa=4+9<}aXWUuL#ZwN1a;lQod zW{pxWCYGXdEq9qAmvAB904}?97=re$>!I%wxPV#|f#@A*Y=qa%zHlDv^yWbR03%V0 zprLP+b(#fBqxI%FiF*-n8HtH6$8f(P6!H3V^ysgd8de-N(@|K!A< z^qP}jp(RaM9kQ(^K(U8O84?D)aU(g?1S8iWwe)gqpHCaFlJxb*ilr{KTnu4_@5{K- z)n=CCeCrPHO0WHz)dDtkbZfUfVBd?53}K>C5*-wC4hpDN8cGk3lu-ypq+EYpb_2H; z%vP4@&+c2p;thaTs$dc^1CDGlPG@A;yGR5@$UEqk6p58qpw#7lc<+W(WR;(vr(D>W z#(K$vE#uBkT=*q&uaZwzz=P5mjiee6>!lV?c}QIX%ZdkO1dHg>Fa#xcGT6~}1*2m9 zkc7l3ItD6Ie~o_aFjI$Ri=C!8uF4!Ky7iG9QTrxVbsQroi|r)SAon#*B*{}TB-?=@ z8~jJs;_R2iDd!$+n$%X6FO&PYS{YhDAS+U2o4su9x~1+U3z7YN5o0qUK&|g^klZ6X zj_vrM5SUTnz5`*}Hyts9ADwLu#x_L=nv$Z0`HqN`Zo=V>OQI)fh01n~*a%01%cx%0 z4LTFVjmW+ipVQv5rYcn3;d2o4qunWUY!p+?s~X~(ost@WR@r@EuDOSs8*MT4fiP>! zkfo^!PWJJ1MHgKS2D_hc?Bs?isSDO61>ebl$U*9*QY(b=i&rp3@3GV@z>KzcZOxip z^dzA~44;R~cnhWz7s$$v?_8y-k!DZys}Q?4IkSyR!)C0j$(Gm|t#e3|QAOFaV2}36 z?dPNY;@I=FaCwylc_;~kXlZsk$_eLkNb~TIl8QQ`mmH&$*zwwR8zHU*sId)rxHu*K z;yZWa8UmCwju%aSNLwD5fBl^b0Ux1%q8YR*uG`53Mi<`5uA^Dc6Ync)J3N7;zQ*75)hf%a@{$H+%S?SGT)ks60)?6j$ zspl|4Ad6@%-r1t*$tT(en!gIXTUDcsj?28ZEzz)dH)SV3bZ+pjMaW0oc~rOPZP@g! zb9E+ndeVO_Ib9c_>{)`01^`ZS198 z)(t=+{Azi11$eu%aU7jbwuQrO`vLOixuh~%4z@mKr_Oc;F%Uq01fA)^W&y+g16e?rkLhTxV!EqC%2}sx_1u7IBq|}Be&7WI z4I<;1-9tJsI&pQIhj>FPkQV9{(m!wYYV@i5h?A0#BN2wqlEwNDIq06|^2oYVa7<~h zI_OLan0Do*4R5P=a3H9`s5*>xU}_PSztg`+2mv)|3nIy=5#Z$%+@tZnr> zLcTI!Mxa`PY7%{;KW~!=;*t)R_sl<^b>eNO@w#fEt(tPMg_jpJpW$q_DoUlkY|uo> z0-1{ouA#;t%spf*7VjkK&$QrvwUERKt^Sdo)5@?qAP)>}Y!h4(JQ!7{wIdkA+|)bv z&8hBwoX4v|+fie}iTslaBX^i*TjwO}f{V)8*!dMmRPi%XAWc8<_IqK1jUsApk)+~R zNFTCD-h>M5Y{qTQ&0#j@I@tmXGj%rzhTW5%Bkh&sSc=$Fv;M@1y!zvYG5P2(2|(&W zlcbR1{--rJ&s!rB{G-sX5^PaM@3EqWVz_y9cwLR9xMig&9gq(voeI)W&{d6j1jh&< zARXi&APWE1FQWh7eoZjuP z;vdgX>zep^{{2%hem;e*gDJhK1Hj12nBLIJoL<=0+8SVEBx7!4Ea+hBY;A1gBwvY<)tj~T=H`^?3>zeWWm|LAwo*S4Z%bDVUe z6r)CH1H!(>OH#MXFJ2V(U(qxD{4Px2`8qfFLG+=a;B^~Te_Z!r3RO%Oc#ZAHKQxV5 zRYXxZ9T2A%NVJIu5Pu7!Mj>t%YDO$T@M=RR(~mi%sv(YXVl`yMLD;+WZ{vG9(@P#e zMo}ZiK^7^h6TV%cG+;jhJ0s>h&VERs=tuZz^Tlu~%d{ZHtq6hX$V9h)Bw|jVCMudd zwZ5l7In8NT)qEPGF$VSKg&fb0%R2RnUnqa){)V(X(s0U zkCdVZe6wy{+_WhZh3qLp245Y2RR$@g-!9PjJ&4~0cFSHMUn=>dapv)hy}|y91ZWTV zCh=z*!S3_?`$&-eZ6xIXUq8RGl9oK0BJw*TdU6A`LJqX9eS3X@F)g$jLkBWFscPhR zpCv8#KeAc^y>>Y$k^=r|K(DTC}T$0#jQBOwB#@`P6~*IuW_8JxCG}J4va{ zsZzt}tt+cv7=l&CEuVtjD6G2~_Meh%p4RGuY?hSt?(sreO_F}8r7Kp$qQdvCdZnDQ zxzc*qchE*E2=WK)^oRNa>Ttj`fpvF-JZ5tu5>X1xw)J@1!IqWjq)ESBG?J|ez`-Tc zi5a}GZx|w-h%5lNDE_3ho0hEXMoaofo#Z;$8|2;EDF&*L+e$u}K=u?pb;dv$SXeQM zD-~7P0i_`Wk$#YP$=hw3UVU+=^@Kuy$>6?~gIXx636jh{PHly_a2xNYe1l60`|y!7 z(u%;ILuW0DDJ)2%y`Zc~hOALnj1~txJtcdD#o4BCT68+8gZe`=^te6H_egxY#nZH&P*)hgYaoJ^qtmpeea`35Fw)cy!w@c#v6E29co8&D9CTCl%^GV|X;SpneSXzV~LXyRn-@K0Df z{tK-nDWA!q38M1~`xUIt_(MO^R(yNY#9@es9RQbY@Ia*xHhD&=k^T+ zJi@j2I|WcgW=PuAc>hs`(&CvgjL2a9Rx zCbZyUpi8NWUOi@S%t+Su4|r&UoU|ze9SVe7p@f1GBkrjkkq)T}X%Qo1g!SQ{O{P?m z-OfGyyWta+UCXH+-+(D^%kw#A1-U;?9129at7MeCCzC{DNgO zeSqsV>W^NIfTO~4({c}KUiuoH8A*J!Cb0*sp*w-Bg@YfBIPZFH!M}C=S=S7PLLcIG zs7K77g~W)~^|+mx9onzMm0qh(f~OsDTzVmRtz=aZTllgR zGUn~_5hw_k&rll<4G=G+`^Xlnw;jNYDJz@bE?|r866F2hA9v0-8=JO3g}IHB#b`hy zA42a0>{0L7CcabSD+F7?pGbS1KMvT{@1_@k!_+Ki|5~EMGt7T%u=79F)8xEiL5!EJ zzuxQ`NBliCoJMJdwu|);zRCD<5Sf?Y>U$trQ-;xj6!s5&w=9E7)%pZ+1Nh&8nCCwM zv5>Ket%I?cxr3vVva`YeR?dGxbG@pi{H#8@kFEf0Jq6~K4>kt26*bxv=P&jyE#e$| zDJB_~imk^-z|o!2njF2hL*|7sHCnzluhJjwLQGDmC)Y9 zr9ZN`s)uCd^XDvn)VirMgW~qfn1~SaN^7vcX#K1G`==UGaDVVx$0BQnubhX|{e z^i0}>k-;BP#Szk{cFjO{2x~LjK{^Upqd&<+03_iMLp0$!6_$@TbX>8U-f*-w-ew1?`CtD_0y_Lo|PfKi52p?`5$Jzx0E8`M0 zNIb?#!K$mM4X%`Ry_yhG5k@*+n4||2!~*+&pYLh~{`~o(W|o64^NrjP?-1Lgu?iK^ zTX6u3?#$?R?N!{599vg>G8RGHw)Hx&=|g4599y}mXNpM{EPKKXB&+m?==R3GsIq?G zL5fH={=zawB(sMlDBJ+{dgb)Vx3pu>L=mDV0{r1Qs{0Pn%TpopH{m(By4;{FBvi{I z$}x!Iw~MJOL~&)p93SDIfP3x%ROjg}X{Sme#hiJ&Yk&a;iR}V|n%PriZBY8SX2*;6 z4hdb^&h;Xz%)BDACY5AUsV!($lib4>11UmcgXKWpzRL8r2Srl*9Y(1uBQsY&hO&uv znDNff0tpHlLISam?o(lOp#CmFdH<6HmA0{UwfU#Y{8M+7od8b8|B|7ZYR9f<#+V|ZSaCQvI$~es~g(Pv{2&m_rKSB2QQ zMvT}$?Ll>V+!9Xh5^iy3?UG;dF-zh~RL#++roOCsW^cZ&({6q|?Jt6`?S8=16Y{oH zp50I7r1AC1(#{b`Aq5cw>ypNggHKM9vBx!W$eYIzD!4KbLsZGr2o8>g<@inmS3*>J zx8oG((8f!ei|M@JZB`p7+n<Q}?>h249<`7xJ?u}_n;Gq(&km#1ULN87CeTO~FY zS_Ty}0TgQhV zOh3T7{{x&LSYGQfKR1PDIkP!WnfC1$l+fs@Di+d4O=eVKeF~2fq#1<8hEvpwuqcaH z4A8u~r^gnY3u6}zj*RHjk{AHhrrDqaj?|6GaVJbV%o-nATw}ASFr!f`Oz|u_QPkR# z0mDudY1dZRlk@TyQ?%Eti=$_WNFtLpSx9=S^be{wXINp%MU?a`F66LNU<c;0&ngifmP9i;bj6&hdGMW^Kf8e6ZDXbQD&$QAAMo;OQ)G zW(qlHh;}!ZP)JKEjm$VZjTs@hk&4{?@+NADuYrr!R^cJzU{kGc1yB?;7mIyAWwhbeA_l_lw-iDVi7wcFurf5 z#Uw)A@a9fOf{D}AWE%<`s1L_AwpZ?F!Vac$LYkp<#A!!`XKaDC{A%)~K#5z6>Hv@V zBEqF(D5?@6r3Pwj$^krpPDCjB+UOszqUS;b2n>&iAFcw<*im2(b3|5u6SK!n9Sg4I z0KLcwA6{Mq?p%t>aW0W!PQ>iUeYvNjdKYqII!CE7SsS&Rj)eIw-K4jtI?II+0IdGq z2WT|L3RL?;GtGgt1LWfI4Ka`9dbZXc$TMJ~8#Juv@K^1RJN@yzdLS8$AJ(>g!U9`# zx}qr7JWlU+&m)VG*Se;rGisutS%!6yybi%B`bv|9rjS(xOUIvbNz5qtvC$_JYY+c& za*3*2$RUH8p%pSq>48xR)4qsp!Q7BEiJ*`^>^6INRbC@>+2q9?x(h0bpc>GaNFi$K zPH$6!#(~{8@0QZk=)QnM#I=bDx5vTvjm$f4K}%*s+((H2>tUTf==$wqyoI`oxI7>C z&>5fe)Yg)SmT)eA(|j@JYR1M%KixxC-Eceknf-;N=jJTwKvk#@|J^&5H0c+%KxHUI z6dQbwwVx3p?X<_VRVb2fStH?HH zFR@Mp=qX%#L3XL)+$PXKV|o|#DpHAoqvj6uQKe@M-mnhCSou7Dj4YuO6^*V`m)1lf z;)@e%1!Qg$10w8uEmz{ENb$^%u}B;J7sDd zump}onoD#!l=agcBR)iG!3AF0-63%@`K9G(CzKrm$VJ{v7^O9Ps7Zej|3m= zVXlR&yW6=Y%mD30G@|tf=yC7-#L!16Q=dq&@beWgaIL40k0n% z)QHrp2Jck#evLMM1RGt3WvQ936ZC9vEje0nFMfvmOHVI+&okB_K|l-;|4vW;qk>n~ z+|kk8#`K?x`q>`(f6A${wfw9Cx(^)~tX7<#TpxR#zYG2P+FY~mG{tnEkv~d6oUQA+ z&hNTL=~Y@rF`v-RZlts$nb$3(OL1&@Y11hhL9+zUb6)SP!;CD)^GUtUpCHBE`j1te zAGud@miCVFLk$fjsrcpjsadP__yj9iEZUW{Ll7PPi<$R;m1o!&Xdl~R_v0;oDX2z^!&8}zNGA}iYG|k zmehMd1%?R)u6R#<)B)1oe9TgYH5-CqUT8N7K-A-dm3hbm_W21p%8)H{O)xUlBVb+iUR}-v5dFaCyfSd zC6Bd7=N4A@+Bna=!-l|*_(nWGDpoyU>nH=}IOrLfS+-d40&(Wo*dDB9nQiA2Tse$R z;uq{`X7LLzP)%Y9aHa4YQ%H?htkWd3Owv&UYbr5NUDAH^<l@Z0Cx%`N+B*i!!1u>D8%;Qt1$ zE5O0{-`9gdDxZ!`0m}ywH!;c{oBfL-(BH<&SQ~smbcobU!j49O^f4&IIYh~f+hK*M zZwTp%{ZSAhMFj1qFaOA+3)p^gnXH^=)`NTYgTu!CLpEV2NF=~-`(}7p^Eof=@VUbd z_9U|8qF7Rueg&$qpSSkN%%%DpbV?8E8ivu@ensI0toJ7Eas^jyFReQ1JeY9plb^{m z&eQO)qPLZQ6O;FTr*aJq=$cMN)QlQO@G&%z?BKUs1&I^`lq>=QLODwa`(mFGC`0H< zOlc*|N?B5&!U6BuJvkL?s1&nsi$*5cCv7^j_*l&$-sBmRS85UIrE--7eD8Gr3^+o? zqG-Yl4S&E;>H>k^a0GdUI(|n1`ws@)1%sq2XBdK`mqrNq_b4N{#VpouCXLzNvjoFv zo9wMQ6l0+FT+?%N(ka*;%m~(?338bu32v26!{r)|w8J`EL|t$}TA4q_FJRX5 zCPa{hc_I(7TGE#@rO-(!$1H3N-C0{R$J=yPCXCtGk{4>=*B56JdXU9cQVwB`6~cQZ zf^qK21x_d>X%dT!!)CJQ3mlHA@ z{Prkgfs6=Tz%63$6Zr8CO0Ak3A)Cv#@BVKr&aiKG7RYxY$Yx>Bj#3gJk*~Ps-jc1l z;4nltQwwT4@Z)}Pb!3xM?+EW0qEKA)sqzw~!C6wd^{03-9aGf3Jmt=}w-*!yXupLf z;)>-7uvWN4Unn8b4kfIza-X=x*e4n5pU`HtgpFFd))s$C@#d>aUl3helLom+RYb&g zI7A9GXLRZPl}iQS*d$Azxg-VgcUr*lpLnbPKUV{QI|bsG{8bLG<%CF( zMoS4pRDtLVYOWG^@ox^h8xL~afW_9DcE#^1eEC1SVSb1BfDi^@g?#f6e%v~Aw>@w- zIY0k+2lGWNV|aA*e#`U3=+oBDmGeInfcL)>*!w|*;mWiKNG6wP6AW4-4imN!W)!hE zA02~S1*@Q`fD*+qX@f3!2yJX&6FsEfPditB%TWo3=HA;T3o2IrjS@9SSxv%{{7&4_ zdS#r4OU41~GYMiib#z#O;zohNbhJknrPPZS6sN$%HB=jUnlCO_w5Gw5EeE@KV>soy z2EZ?Y|4RQDDjt5y!WBlZ(8M)|HP<0YyG|D%RqD+K#e7-##o3IZxS^wQ5{Kbzb6h(i z#(wZ|^ei>8`%ta*!2tJzwMv+IFHLF`zTU8E^Mu!R*45_=ccqI};Zbyxw@U%a#2}%f zF>q?SrUa_a4H9l+uW8JHh2Oob>NyUwG=QH~-^ZebU*R@67DcXdz2{HVB4#@edz?B< z5!rQH3O0>A&ylROO%G^fimV*LX7>!%re{_Sm6N>S{+GW1LCnGImHRoF@csnFzn@P0 zM=jld0z%oz;j=>c7mMwzq$B^2mae7NiG}%>(wtmsDXkWk{?BeMpTrIt3Mizq?vRsf zi_WjNp+61uV(%gEU-Vf0;>~vcDhe(dzWdaf#4mH3o^v{0EWhj?E?$5v02sV@xL0l4 zX0_IMFtQ44PfWBbPYN#}qxa%=J%dlR{O!KyZvk^g5s?sTNycWYPJ^FK(nl3k?z-5t z39#hKrdO7V(@!TU)LAPY&ngnZ1MzLEeEiZznn7e-jLCy8LO zu^7_#z*%I-BjS#Pg-;zKWWqX-+Ly$T!4`vTe5ZOV0j?TJVA*2?*=82^GVlZIuH%9s zXiV&(T(QGHHah=s&7e|6y?g+XxZGmK55`wGV>@1U)Th&=JTgJq>4mI&Av2C z)w+kRoj_dA!;SfTfkgMPO>7Dw6&1*Hi1q?54Yng`JO&q->^CX21^PrU^JU#CJ_qhV zSG>afB%>2fx<~g8p=P8Yzxqc}s@>>{g7}F!;lCXvF#RV)^fyYb_)iKVCz1xEq=fJ| z0a7DMCK*FuP=NM*5h;*D`R4y$6cpW-E&-i{v`x=Jbk_xSn@2T3q!3HoAOB`@5Vg6) z{PW|@9o!e;v1jZ2{=Uw6S6o{g82x6g=k!)cFSC*oemHaVjg?VpEmtUuD2_J^A~$4* z3O7HsbA6wxw{TP5Kk)(Vm?gKo+_}11vbo{Tp_5x79P~#F)ahQXT)tSH5;;14?s)On zel1J>1x>+7;g1Iz2FRpnYz;sD0wG9Q!vuzE9yKi3@4a9Nh1!GGN?hA)!mZEnnHh&i zf?#ZEN2sFbf~kV;>K3UNj1&vFhc^sxgj8FCL4v>EOYL?2uuT`0eDH}R zmtUJMxVrV5H{L53hu3#qaWLUa#5zY?f5ozIn|PkMWNP%n zWB5!B0LZB0kLw$k39=!akkE9Q>F4j+q434jB4VmslQ;$ zKiO#FZ`p|dKS716jpcvR{QJkSNfDVhr2%~eHrW;fU45>>snr*S8Vik-5eN5k*c2Mp zyxvX&_cFbB6lODXznHHT|rsURe2!swomtrqc~w5 zymTM8!w`1{04CBprR!_F{5LB+2_SOuZN{b*!J~1ZiPpP-M;);!ce!rOPDLtgR@Ie1 zPreuqm4!H)hYePcW1WZ0Fyaqe%l}F~Orr)~+;mkS&pOhP5Ebb`cnUt!X_QhP4_4p( z8YKQCDKGIy>?WIFm3-}Br2-N`T&FOi?t)$hjphB9wOhBXU#Hb+zm&We_-O)s(wc`2 z8?VsvU;J>Ju7n}uUb3s1yPx_F*|FlAi=Ge=-kN?1;`~6szP%$3B0|8Sqp%ebM)F8v zADFrbeT0cgE>M0DMV@_Ze*GHM>q}wWMzt|GYC%}r{OXRG3Ij&<+nx9;4jE${Fj_r* z`{z1AW_6Myd)i6e0E-h&m{{CvzH=Xg!&(bLYgRMO_YVd8JU7W+7MuGWNE=4@OvP9+ zxi^vqS@5%+#gf*Z@RVyU9N1sO-(rY$24LGsg1>w>s6ST^@)|D9>cT50maXLUD{Fzf zt~tp{OSTEKg3ZSQyQQ5r51){%=?xlZ54*t1;Ow)zLe3i?8tD8YyY^k%M)e`V*r+vL zPqUf&m)U+zxps+NprxMHF{QSxv}>lE{JZETNk1&F+R~bp{_T$dbXL2UGnB|hgh*p4h$clt#6;NO~>zuyY@C-MD@)JCc5XrYOt`wW7! z_ti2hhZBMJNbn0O-uTxl_b6Hm313^fG@e;RrhIUK9@# z+DHGv_Ow$%S8D%RB}`doJjJy*aOa5mGHVHz0e0>>O_%+^56?IkA5eN+L1BVCp4~m=1eeL zb;#G!#^5G%6Mw}r1KnaKsLvJB%HZL)!3OxT{k$Yo-XrJ?|7{s4!H+S2o?N|^Z z)+?IE9H7h~Vxn5hTis^3wHYuOU84+bWd)cUKuHapq=&}WV#OxHpLab`NpwHm8LmOo zjri+!k;7j_?FP##CpM+pOVx*0wExEex z@`#)K<-ZrGyArK;a%Km`^+We|eT+#MygHOT6lXBmz`8|lyZOwL1+b+?Z$0OhMEp3R z&J=iRERpv~TC=p2-BYLC*?4 zxvPs9V@g=JT0>zky5Poj=fW_M!c)Xxz1<=&_ZcL=LMZJqlnO1P^xwGGW*Z+yTBvbV z-IFe6;(k1@$1;tS>{%pXZ_7w+i?N4A2=TXnGf=YhePg8bH8M|Lk-->+w8Y+FjZ;L=wSGwxfA`gqSn)f(XNuSm>6Y z@|#e-)I(PQ^G@N`%|_DZSb4_pkaEF0!-nqY+t#pyA>{9^*I-zw4SYA1_z2Bs$XGUZbGA;VeMo%CezHK0lO={L%G)dI-+8w?r9iexdoB{?l zbJ}C?huIhWXBVs7oo{!$lOTlvCLZ_KN1N+XJGuG$rh<^eUQIqcI7^pmqhBSaOKNRq zrx~w^?9C?*&rNwP_SPYmo;J-#!G|{`$JZK7DxsM3N^8iR4vvn>E4MU&Oe1DKJvLc~ zCT>KLZ1;t@My zRj_2hI^61T&LIz)S!+AQIV23n1>ng+LUvzv;xu!4;wpqb#EZz;F)BLUzT;8UA1x*6vJ zicB!3Mj03s*kGV{g`fpC?V^s(=JG-k1EMHbkdP4P*1^8p_TqO|;!Zr%GuP$8KLxuf z=pv*H;kzd;P|2`JmBt~h6|GxdU~@weK5O=X&5~w$HpfO}@l-T7@vTCxVOwCkoPQv8 z@aV_)I5HQtfs7^X=C03zYmH4m0S!V@JINm6#(JmZRHBD?T!m^DdiZJrhKpBcur2u1 zf9e4%k$$vcFopK5!CC`;ww(CKL~}mlxK_Pv!cOsFgVkNIghA2Au@)t6;Y3*2gK=5d z?|@1a)-(sQ%uFOmJ7v2iG&l&m^u&^6DJM#XzCrF%r>{2XKyxLD2rgWBD;i(!e4InDQBDg==^z;AzT2z~OmV0!?Z z0S9pX$+E;w3WN;v&NYT=+G8hf=6w0E1$0AOr61}eOvE8W1jX%>&Mjo7&!ulawgzLH zbcb+IF(s^3aj12WSi#pzIpijJJzkP?JzRawnxmNDSUR#7!29vHULCE<3Aa#be}ie~d|!V+ z%l~s9Odo$G&fH!t!+`rUT0T9DulF!Yq&BfQWFZV1L9D($r4H(}Gnf6k3^wa7g5|Ws zj7%d`!3(0bb55yhC6@Q{?H|2os{_F%o=;-h{@Yyyn*V7?{s%Grvpe!H^kl6tF4Zf5 z{Jv1~yZ*iIWL_9C*8pBMQArfJJ0d9Df6Kl#wa}7Xa#Ef_5B7=X}DzbQXVPfCwTO@9+@;A^Ti6il_C>g?A-GFwA0#U;t4;wOm-4oS})h z5&on>NAu67O?YCQr%7XIzY%LS4bha9*e*4bU4{lGCUmO2UQ2U)QOqClLo61Kx~3dI zmV3*(P6F_Tr-oP%x!0kTnnT?Ep5j;_IQ^pTRp=e8dmJtI4YgWd0}+b2=ATkOhgpXe z;jmw+FBLE}UIs4!&HflFr4)vMFOJ19W4f2^W(=2)F%TAL)+=F>IE$=e=@j-*bFLSg z)wf|uFQu+!=N-UzSef62u0-C8Zc7 zo6@F)c+nZA{H|+~7i$DCU0pL{0Ye|fKLuV^w!0Y^tT$isu%i1Iw&N|tX3kwFKJN(M zXS`k9js66o$r)x?TWL}Kxl`wUDUpwFx(w4Yk%49;$sgVvT~n8AgfG~HUcDt1TRo^s zdla@6heJB@JV z!vK;BUMznhzGK6PVtj0)GB=zTv6)Q9Yt@l#fv7>wKovLobMV-+(8)NJmyF8R zcB|_K7=FJGGn^X@JdFaat0uhKjp3>k#^&xE_}6NYNG?kgTp>2Iu?ElUjt4~E-?`Du z?mDCS9wbuS%fU?5BU@Ijx>1HG*N?gIP+<~xE4u=>H`8o((cS5M6@_OK%jSjFHirQK zN9@~NXFx*jS{<|bgSpC|SAnA@I)+GB=2W|JJChLI_mx+-J(mSJ!b)uUom6nH0#2^(L@JBlV#t zLl?j54s`Y3vE^c_3^Hl0TGu*tw_n?@HyO@ZrENxA+^!)OvUX28gDSF*xFtQzM$A+O zCG=n#6~r|3zt=8%GuG} z<#VCZ%2?3Q(Ad#Y7GMJ~{U3>E{5e@z6+rgZLX{Cxk^p-7dip^d29;2N1_mm4QkASo z-L`GWWPCq$uCo;X_BmGIpJFBlhl<8~EG{vOD1o|X$aB9KPhWO_cKiU*$HWEgtf=fn zsO%9bp~D2c@?*K9jVN@_vhR03>M_8h!_~%aN!Cnr?s-!;U3SVfmhRwk11A^8Ns`@KeE}+ zN$H}a1U6E;*j5&~Og!xHdfK5M<~xka)x-0N)K_&e7AjMz`toDzasH+^1bZlC!n()crk9kg@$(Y{wdKvbuUd04N^8}t1iOgsKF zGa%%XWx@WoVaNC1!|&{5ZbkopFre-Lu(LCE5HWZBoE#W@er9W<>R=^oYxBvypN#x3 zq#LC8&q)GFP=5^-bpHj?LW=)-g+3_)Ylps!3^YQ{9~O9&K)xgy zMkCWaApU-MI~e^cV{Je75Qr7eF%&_H)BvfyKL=gIA>;OSq(y z052BFz3E(Prg~09>|_Z@!qj}@;8yxnw+#Ej0?Rk<y}4ghbD569B{9hSFr*^ygZ zr6j7P#gtZh6tMk6?4V$*Jgz+#&ug;yOr>=qdI#9U&^am2qoh4Jy}H2%a|#Fs{E(5r z%!ijh;VuGA6)W)cJZx+;9Bp1LMUzN~x_8lQ#D3+sL{be-Jyeo@@dv7XguJ&S5vrH` z>QxOMWn7N-T!D@1(@4>ZlL^y5>m#0!HKovs12GRav4z!>p(1~xok8+_{| z#Ae4{9#NLh#Vj2&JuIn5$d6t@__`o}umFo(n0QxUtd2GKCyE+erwXY?`cm*h&^9*8 zJ+8x6fRZI-e$CRygofIQN^dWysCxgkyr{(_oBwwSRxZora1(%(aC!5BTtj^+YuevI zx?)H#(xlALUp6QJ!=l9N__$cxBZ5p&7;qD3PsXRFVd<({Kh+mShFWJNpy`N@ab7?9 zv5=klvCJ4bx|-pvOO2-+G)6O?$&)ncA#Urze2rlBfp#htudhx-NeRnJ@u%^_bfw4o z4|{b8SkPV3b>Wera1W(+N@p9H>dc6{cnkh-sgr?e%(YkWvK+0YXVwk0=d`)}*47*B z5JGkEdVix!w7-<%r0JF~`ZMMPe;f0EQHuYHxya`puazyph*ZSb1mJAt^k4549BfS; zK7~T&lRb=W{s&t`DJ$B}s-eH1&&-wEOH1KWsKn0a(ZI+G!v&W4A*cl>qAvUv6pbUR z#(f#EKV8~hk&8oayBz4vaswc(?qw1vn`yC zZQDl2PCB-&Uu@g9ZQHhO+v(W0bNig{-k0;;`+wM@#@J)8r?qOYs#&vUna8ILxN7S{ zp1s41KnR8miQJtJtOr|+qk}wrLt+N*z#5o`TmD1)E&QD(Vh&pjZJ_J*0!8dy_ z>^=@v=J)C`x&gjqAYu`}t^S=DFCtc0MkBU2zf|69?xW`Ck~(6zLD)gSE{7n~6w8j_ zoH&~$ED2k5-yRa0!r8fMRy z;QjBYUaUnpd}mf%iVFPR%Dg9!d>g`01m~>2s))`W|5!kc+_&Y>wD@@C9%>-lE`WB0 zOIf%FVD^cj#2hCkFgi-fgzIfOi+ya)MZK@IZhHT5FVEaSbv-oDDs0W)pA0&^nM0TW zmgJmd7b1R7b0a`UwWJYZXp4AJPteYLH>@M|xZFKwm!t3D3&q~av?i)WvAKHE{RqpD{{%OhYkK?47}+}` zrR2(Iv9bhVa;cDzJ%6ntcSbx7v7J@Y4x&+eWSKZ*eR7_=CVIUSB$^lfYe@g+p|LD{ zPSpQmxx@b$%d!05|H}WzBT4_cq?@~dvy<7s&QWtieJ9)hd4)$SZz}#H2UTi$CkFWW|I)v_-NjuH!VypONC=1`A=rm_jfzQ8Fu~1r8i{q-+S_j$ z#u^t&Xnfi5tZtl@^!fUJhx@~Cg0*vXMK}D{>|$#T*+mj(J_@c{jXBF|rm4-8%Z2o! z2z0o(4%8KljCm^>6HDK!{jI7p+RAPcty_~GZ~R_+=+UzZ0qzOwD=;YeZt*?3%UGdr z`c|BPE;yUbnyARUl&XWSNJ<+uRt%!xPF&K;(l$^JcA_CMH6)FZt{>6ah$|(9$2fc~ z=CD00uHM{qv;{Zk9FR0~u|3|Eiqv9?z2#^GqylT5>6JNZwKqKBzzQpKU2_pmtD;CT zi%Ktau!Y2Tldfu&b0UgmF(SSBID)15*r08eoUe#bT_K-G4VecJL2Pa=6D1K6({zj6 za(2Z{r!FY5W^y{qZ}08+h9f>EKd&PN90f}Sc0ejf%kB4+f#T8Q1=Pj=~#pi$U zp#5rMR%W25>k?<$;$x72pkLibu1N|jX4cWjD3q^Pk3js!uK6h7!dlvw24crL|MZs_ zb%Y%?Fyp0bY0HkG^XyS76Ts*|Giw{31LR~+WU5NejqfPr73Rp!xQ1mLgq@mdWncLy z%8}|nzS4P&`^;zAR-&nm5f;D-%yNQPwq4N7&yULM8bkttkD)hVU>h>t47`{8?n2&4 zjEfL}UEagLUYwdx0sB2QXGeRmL?sZ%J!XM`$@ODc2!y|2#7hys=b$LrGbvvjx`Iqi z&RDDm3YBrlKhl`O@%%&rhLWZ*ABFz2nHu7k~3@e4)kO3%$=?GEFUcCF=6-1n!x^vmu+Ai*amgXH+Rknl6U>#9w;A} zn2xanZSDu`4%%x}+~FG{Wbi1jo@wqBc5(5Xl~d0KW(^Iu(U3>WB@-(&vn_PJt9{1`e9Iic@+{VPc`vP776L*viP{wYB2Iff8hB%E3|o zGMOu)tJX!`qJ}ZPzq7>=`*9TmETN7xwU;^AmFZ-ckZjV5B2T09pYliaqGFY|X#E-8 z20b>y?(r-Fn5*WZ-GsK}4WM>@TTqsxvSYWL6>18q8Q`~JO1{vLND2wg@58OaU!EvT z1|o+f1mVXz2EKAbL!Q=QWQKDZpV|jznuJ}@-)1&cdo z^&~b4Mx{*1gurlH;Vhk5g_cM&6LOHS2 zRkLfO#HabR1JD4Vc2t828dCUG#DL}f5QDSBg?o)IYYi@_xVwR2w_ntlpAW0NWk$F1 z$If?*lP&Ka1oWfl!)1c3fl`g*lMW3JOn#)R1+tfwrs`aiFUgz3;XIJ>{QFxLCkK30 zNS-)#DON3yb!7LBHQJ$)4y%TN82DC2-9tOIqzhZ27@WY^<6}vXCWcR5iN{LN8{0u9 zNXayqD=G|e?O^*ms*4P?G%o@J1tN9_76e}E#66mr89%W_&w4n66~R;X_vWD(oArwj z4CpY`)_mH2FvDuxgT+akffhX0b_slJJ*?Jn3O3~moqu2Fs1oL*>7m=oVek2bnprnW zixkaIFU%+3XhNA@@9hyhFwqsH2bM|`P?G>i<-gy>NflhrN{$9?LZ1ynSE_Mj0rADF zhOz4FnK}wpLmQuV zgO4_Oz9GBu_NN>cPLA=`SP^$gxAnj;WjJnBi%Q1zg`*^cG;Q)#3Gv@c^j6L{arv>- zAW%8WrSAVY1sj$=umcAf#ZgC8UGZGoamK}hR7j6}i8#np8ruUlvgQ$j+AQglFsQQq zOjyHf22pxh9+h#n$21&$h?2uq0>C9P?P=Juw0|;oE~c$H{#RGfa>| zj)Iv&uOnaf@foiBJ}_;zyPHcZt1U~nOcNB{)og8Btv+;f@PIT*xz$x!G?u0Di$lo7 zOugtQ$Wx|C($fyJTZE1JvR~i7LP{ zbdIwqYghQAJi9p}V&$=*2Azev$6K@pyblphgpv8^9bN!?V}{BkC!o#bl&AP!3DAjM zmWFsvn2fKWCfjcAQmE+=c3Y7j@#7|{;;0f~PIodmq*;W9Fiak|gil6$w3%b_Pr6K_ zJEG@&!J%DgBZJDCMn^7mk`JV0&l07Bt`1ymM|;a)MOWz*bh2#d{i?SDe9IcHs7 zjCrnyQ*Y5GzIt}>`bD91o#~5H?4_nckAgotN{2%!?wsSl|LVmJht$uhGa+HiH>;av z8c?mcMYM7;mvWr6noUR{)gE!=i7cZUY7e;HXa221KkRoc2UB>s$Y(k%NzTSEr>W(u z<(4mcc)4rB_&bPzX*1?*ra%VF}P1nwiP5cykJ&W{!OTlz&Td0pOkVp+wc z@k=-Hg=()hNg=Q!Ub%`BONH{ z_=ZFgetj@)NvppAK2>8r!KAgi>#%*7;O-o9MOOfQjV-n@BX6;Xw;I`%HBkk20v`qoVd0)}L6_49y1IhR z_OS}+eto}OPVRn*?UHC{eGyFU7JkPz!+gX4P>?h3QOwGS63fv4D1*no^6PveUeE5% zlehjv_3_^j^C({a2&RSoVlOn71D8WwMu9@Nb@=E_>1R*ve3`#TF(NA0?d9IR_tm=P zOP-x;gS*vtyE1Cm zG0L?2nRUFj#aLr-R1fX*$sXhad)~xdA*=hF3zPZhha<2O$Ps+F07w*3#MTe?)T8|A!P!v+a|ot{|^$q(TX`35O{WI0RbU zCj?hgOv=Z)xV?F`@HKI11IKtT^ocP78cqHU!YS@cHI@{fPD?YXL)?sD~9thOAv4JM|K8OlQhPXgnevF=F7GKD2#sZW*d za}ma31wLm81IZxX(W#A9mBvLZr|PoLnP>S4BhpK8{YV_}C|p<)4#yO{#ISbco92^3 zv&kCE(q9Wi;9%7>>PQ!zSkM%qqqLZW7O`VXvcj;WcJ`2~v?ZTYB@$Q&^CTfvy?1r^ z;Cdi+PTtmQwHX_7Kz?r#1>D zS5lWU(Mw_$B&`ZPmqxpIvK<~fbXq?x20k1~9az-Q!uR78mCgRj*eQ>zh3c$W}>^+w^dIr-u{@s30J=)1zF8?Wn|H`GS<=>Om|DjzC{}Jt?{!fSJe*@$H zg>wFnlT)k#T?LslW zu$^7Uy~$SQ21cE?3Ijl+bLfuH^U5P^$@~*UY#|_`uvAIe(+wD2eF}z_y!pvomuVO; zS^9fbdv)pcm-B@CW|Upm<7s|0+$@@<&*>$a{aW+oJ%f+VMO<#wa)7n|JL5egEgoBv zl$BY(NQjE0#*nv=!kMnp&{2Le#30b)Ql2e!VkPLK*+{jv77H7)xG7&=aPHL7LK9ER z5lfHxBI5O{-3S?GU4X6$yVk>lFn;ApnwZybdC-GAvaznGW-lScIls-P?Km2mF>%B2 zkcrXTk+__hj-3f48U%|jX9*|Ps41U_cd>2QW81Lz9}%`mTDIhE)jYI$q$ma7Y-`>% z8=u+Oftgcj%~TU}3nP8&h7k+}$D-CCgS~wtWvM|UU77r^pUw3YCV80Ou*+bH0!mf0 zxzUq4ed6y>oYFz7+l18PGGzhB^pqSt)si=9M>~0(Bx9*5r~W7sa#w+_1TSj3Jn9mW zMuG9BxN=}4645Cpa#SVKjFst;9UUY@O<|wpnZk$kE+to^4!?0@?Cwr3(>!NjYbu?x z1!U-?0_O?k!NdM^-rIQ8p)%?M+2xkhltt*|l=%z2WFJhme7*2xD~@zk#`dQR$6Lmd zb3LOD4fdt$Cq>?1<%&Y^wTWX=eHQ49Xl_lFUA(YQYHGHhd}@!VpYHHm=(1-O=yfK#kKe|2Xc*9}?BDFN zD7FJM-AjVi)T~OG)hpSWqH>vlb41V#^G2B_EvYlWhDB{Z;Q9-0)ja(O+By`31=biA zG&Fs#5!%_mHi|E4Nm$;vVQ!*>=_F;ZC=1DTPB#CICS5fL2T3XmzyHu?bI;m7D4@#; ztr~;dGYwb?m^VebuULtS4lkC_7>KCS)F@)0OdxZIFZp@FM_pHnJes8YOvwB|++#G( z&dm*OP^cz95Wi15vh`Q+yB>R{8zqEhz5of>Po$9LNE{xS<)lg2*roP*sQ}3r3t<}; zPbDl{lk{pox~2(XY5=qg0z!W-x^PJ`VVtz$git7?)!h>`91&&hESZy1KCJ2nS^yMH z!=Q$eTyRi68rKxdDsdt+%J_&lapa{ds^HV9Ngp^YDvtq&-Xp}60B_w@Ma>_1TTC;^ zpbe!#gH}#fFLkNo#|`jcn?5LeUYto%==XBk6Ik0kc4$6Z+L3x^4=M6OI1=z5u#M%0 z0E`kevJEpJjvvN>+g`?gtnbo$@p4VumliZV3Z%CfXXB&wPS^5C+7of2tyVkMwNWBiTE2 z8CdPu3i{*vR-I(NY5syRR}I1TJOV@DJy-Xmvxn^IInF>Tx2e)eE9jVSz69$6T`M9-&om!T+I znia!ZWJRB28o_srWlAxtz4VVft8)cYloIoVF=pL zugnk@vFLXQ_^7;%hn9x;Vq?lzg7%CQR^c#S)Oc-8d=q_!2ZVH764V z!wDKSgP}BrVV6SfCLZnYe-7f;igDs9t+K*rbMAKsp9L$Kh<6Z;e7;xxced zn=FGY<}CUz31a2G}$Q(`_r~75PzM4l_({Hg&b@d8&jC}B?2<+ed`f#qMEWi z`gm!STV9E4sLaQX+sp5Nu9*;9g12naf5?=P9p@H@f}dxYprH+3ju)uDFt^V{G0APn zS;16Dk{*fm6&BCg#2vo?7cbkkI4R`S9SSEJ=#KBk3rl69SxnCnS#{*$!^T9UUmO#&XXKjHKBqLdt^3yVvu8yn|{ zZ#%1CP)8t-PAz(+_g?xyq;C2<9<5Yy<~C74Iw(y>uUL$+$mp(DRcCWbCKiGCZw@?_ zdomfp+C5xt;j5L@VfhF*xvZdXwA5pcdsG>G<8II-|1dhAgzS&KArcb0BD4ZZ#WfiEY{hkCq5%z9@f|!EwTm;UEjKJsUo696V>h zy##eXYX}GUu%t{Gql8vVZKkNhQeQ4C%n|RmxL4ee5$cgwlU+?V7a?(jI#&3wid+Kz5+x^G!bb#$q>QpR#BZ}Xo5UW^ zD&I`;?(a}Oys7-`I^|AkN?{XLZNa{@27Dv^s4pGowuyhHuXc zuctKG2x0{WCvg_sGN^n9myJ}&FXyGmUQnW7fR$=bj$AHR88-q$D!*8MNB{YvTTEyS zn22f@WMdvg5~o_2wkjItJN@?mDZ9UUlat2zCh(zVE=dGi$rjXF7&}*sxac^%HFD`Y zTM5D3u5x**{bW!68DL1A!s&$2XG@ytB~dX-?BF9U@XZABO`a|LM1X3HWCllgl0+uL z04S*PX$%|^WAq%jkzp~%9HyYIF{Ym?k)j3nMwPZ=hlCg9!G+t>tf0o|J2%t1 ztC+`((dUplgm3`+0JN~}&FRRJ3?l*>Y&TfjS>!ShS`*MwO{WIbAZR#<%M|4c4^dY8 z{Rh;-!qhY=dz5JthbWoovLY~jNaw>%tS4gHVlt5epV8ekXm#==Po$)}mh^u*cE>q7*kvX&gq)(AHoItMYH6^s6f(deNw%}1=7O~bTHSj1rm2|Cq+3M z93djjdomWCTCYu!3Slx2bZVy#CWDozNedIHbqa|otsUl+ut?>a;}OqPfQA05Yim_2 zs@^BjPoFHOYNc6VbNaR5QZfSMh2S*`BGwcHMM(1@w{-4jVqE8Eu0Bi%d!E*^Rj?cR z7qgxkINXZR)K^=fh{pc0DCKtrydVbVILI>@Y0!Jm>x-xM!gu%dehm?cC6ok_msDVA*J#{75%4IZt}X|tIVPReZS#aCvuHkZxc zHVMtUhT(wp09+w9j9eRqz~LtuSNi2rQx_QgQ(}jBt7NqyT&ma61ldD(s9x%@q~PQl zp6N*?=N$BtvjQ_xIT{+vhb1>{pM0Arde0!X-y))A4znDrVx8yrP3B1(7bKPE5jR@5 zwpzwT4cu~_qUG#zYMZ_!2Tkl9zP>M%cy>9Y(@&VoB84#%>amTAH{(hL4cDYt!^{8L z645F>BWO6QaFJ-{C-i|-d%j7#&7)$X7pv#%9J6da#9FB5KyDhkA+~)G0^87!^}AP>XaCSScr;kL;Z%RSPD2CgoJ;gpYT5&6NUK$86$T?jRH=w8nI9Z534O?5fk{kd z`(-t$8W|#$3>xoMfXvV^-A(Q~$8SKDE^!T;J+rQXP71XZ(kCCbP%bAQ1|%$%Ov9_a zyC`QP3uPvFoBqr_+$HenHklqyIr>PU_Fk5$2C+0eYy^~7U&(!B&&P2%7#mBUhM!z> z_B$Ko?{Pf6?)gpYs~N*y%-3!1>o-4;@1Zz9VQHh)j5U1aL-Hyu@1d?X;jtDBNk*vMXPn@ z+u@wxHN*{uHR!*g*4Xo&w;5A+=Pf9w#PeZ^x@UD?iQ&${K2c}UQgLRik-rKM#Y5rdDphdcNTF~cCX&9ViRP}`>L)QA4zNXeG)KXFzSDa6 zd^St;inY6J_i=5mcGTx4_^Ys`M3l%Q==f>{8S1LEHn{y(kbxn5g1ezt4CELqy)~TV6{;VW>O9?5^ ztcoxHRa0jQY7>wwHWcxA-BCwzsP>63Kt&3fy*n#Cha687CQurXaRQnf5wc9o8v7Rw zNwGr2fac;Wr-Ldehn7tF^(-gPJwPt@VR1f;AmKgxN&YPL;j=0^xKM{!wuU|^mh3NE zy35quf}MeL!PU;|{OW_x$TBothLylT-J>_x6p}B_jW1L>k)ps6n%7Rh z96mPkJIM0QFNYUM2H}YF5bs%@Chs6#pEnloQhEl?J-)es!(SoJpEPoMTdgA14-#mC zghayD-DJWtUu`TD8?4mR)w5E`^EHbsz2EjH5aQLYRcF{l7_Q5?CEEvzDo(zjh|BKg z3aJl_n#j&eFHsUw4~lxqnr!6NL*se)6H=A+T1e3xUJGQrd}oSPwSy5+$tt{2t5J5@(lFxl43amsARG74iyNC}uuS zd2$=(r6RdamdGx^eatX@F2D8?U23tDpR+Os?0Gq2&^dF+$9wiWf?=mDWfjo4LfRwL zI#SRV9iSz>XCSgEj!cW&9H-njJopYiYuq|2w<5R2!nZ27DyvU4UDrHpoNQZiGPkp@ z1$h4H46Zn~eqdj$pWrv;*t!rTYTfZ1_bdkZmVVIRC21YeU$iS-*XMNK`#p8Z_DJx| zk3Jssf^XP7v0X?MWFO{rACltn$^~q(M9rMYoVxG$15N;nP)A98k^m3CJx8>6}NrUd@wp-E#$Q0uUDQT5GoiK_R{ z<{`g;8s>UFLpbga#DAf%qbfi`WN1J@6IA~R!YBT}qp%V-j!ybkR{uY0X|x)gmzE0J z&)=eHPjBxJvrZSOmt|)hC+kIMI;qgOnuL3mbNR0g^<%|>9x7>{}>a2qYSZAGPt4it?8 zNcLc!Gy0>$jaU?}ZWxK78hbhzE+etM`67*-*x4DN>1_&{@5t7_c*n(qz>&K{Y?10s zXsw2&nQev#SUSd|D8w7ZD2>E<%g^; zV{yE_O}gq?Q|zL|jdqB^zcx7vo(^})QW?QKacx$yR zhG|XH|8$vDZNIfuxr-sYFR{^csEI*IM#_gd;9*C+SysUFejP0{{z7@P?1+&_o6=7V|EJLQun^XEMS)w(=@eMi5&bbH*a0f;iC~2J74V2DZIlLUHD&>mlug5+v z6xBN~8-ovZylyH&gG#ptYsNlT?-tzOh%V#Y33zlsJ{AIju`CjIgf$@gr8}JugRq^c zAVQ3;&uGaVlVw}SUSWnTkH_6DISN&k2QLMBe9YU=sA+WiX@z)FoSYX`^k@B!j;ZeC zf&**P?HQG6Rk98hZ*ozn6iS-dG}V>jQhb3?4NJB*2F?6N7Nd;EOOo;xR7acylLaLy z9)^lykX39d@8@I~iEVar4jmjjLWhR0d=EB@%I;FZM$rykBNN~jf>#WbH4U{MqhhF6 zU??@fSO~4EbU4MaeQ_UXQcFyO*Rae|VAPLYMJEU`Q_Q_%s2*>$#S^)&7er+&`9L=1 z4q4ao07Z2Vsa%(nP!kJ590YmvrWg+YrgXYs_lv&B5EcoD`%uL79WyYA$0>>qi6ov7 z%`ia~J^_l{p39EY zv>>b}Qs8vxsu&WcXEt8B#FD%L%ZpcVtY!rqVTHe;$p9rbb5O{^rFMB>auLn-^;s+-&P1#h~mf~YLg$8M9 zZ4#87;e-Y6x6QO<{McUzhy(%*6| z)`D~A(TJ$>+0H+mct(jfgL4x%^oC^T#u(bL)`E2tBI#V1kSikAWmOOYrO~#-cc_8! zCe|@1&mN2{*ceeiBldHCdrURk4>V}79_*TVP3aCyV*5n@jiNbOm+~EQ_}1#->_tI@ zqXv+jj2#8xJtW508rzFrYcJxoek@iW6SR@1%a%Bux&;>25%`j3UI`0DaUr7l79`B1 zqqUARhW1^h6=)6?;@v>xrZNM;t}{yY3P@|L}ey@gG( z9r{}WoYN(9TW&dE2dEJIXkyHA4&pU6ki=rx&l2{DLGbVmg4%3Dlfvn!GB>EVaY_%3+Df{fBiqJV>~Xf8A0aqUjgpa} zoF8YXO&^_x*Ej}nw-$-F@(ddB>%RWoPUj?p8U{t0=n>gAI83y<9Ce@Q#3&(soJ{64 z37@Vij1}5fmzAuIUnXX`EYe;!H-yTVTmhAy;y8VZeB#vD{vw9~P#DiFiKQ|kWwGFZ z=jK;JX*A;Jr{#x?n8XUOLS;C%f|zj-7vXtlf_DtP7bpurBeX%Hjwr z4lI-2TdFpzkjgiv!8Vfv`=SP+s=^i3+N~1ELNWUbH|ytVu>EyPN_3(4TM^QE1swRo zoV7Y_g)a>28+hZG0e7g%@2^s>pzR4^fzR-El}ARTmtu!zjZLuX%>#OoU3}|rFjJg} zQ2TmaygxJ#sbHVyiA5KE+yH0LREWr%^C*yR|@gM$nK2P zo}M}PV0v))uJh&33N>#aU376@ZH79u(Yw`EQ2hM3SJs9f99+cO6_pNW$j$L-CtAfe zYfM)ccwD!P%LiBk!eCD?fHCGvgMQ%Q2oT_gmf?OY=A>&PaZQOq4eT=lwbaf}33LCH zFD|)lu{K7$8n9gX#w4~URjZxWm@wlH%oL#G|I~Fb-v^0L0TWu+`B+ZG!yII)w05DU z>GO?n(TN+B=>HdxVDSlIH76pta$_LhbBg;eZ`M7OGcqt||qi zogS72W1IN%=)5JCyOHWoFP7pOFK0L*OAh=i%&VW&4^LF@R;+K)t^S!96?}^+5QBIs zjJNTCh)?)4k^H^g1&jc>gysM`y^8Rm3qsvkr$9AeWwYpa$b22=yAd1t<*{ zaowSEFP+{y?Ob}8&cwfqoy4Pb9IA~VnM3u!trIK$&&0Op#Ql4j>(EW?UNUv#*iH1$ z^j>+W{afcd`{e&`-A{g}{JnIzYib)!T56IT@YEs{4|`sMpW3c8@UCoIJv`XsAw!XC z34|Il$LpW}CIHFC5e*)}00I5{%OL*WZRGzC0?_}-9{#ue?-ug^ zLE|uv-~6xnSs_2_&CN9{9vyc!Xgtn36_g^wI0C4s0s^;8+p?|mm;Odt3`2ZjwtK;l zfd6j)*Fr#53>C6Y8(N5?$H0ma;BCF3HCjUs7rpb2Kf*x3Xcj#O8mvs#&33i+McX zQpBxD8!O{5Y8D&0*QjD=Yhl9%M0)&_vk}bmN_Ud^BPN;H=U^bn&(csl-pkA+GyY0Z zKV7sU_4n;}uR78ouo8O%g*V;79KY?3d>k6%gpcmQsKk&@Vkw9yna_3asGt`0Hmj59 z%0yiF*`jXhByBI9QsD=+>big5{)BGe&+U2gAARGe3ID)xrid~QN_{I>k}@tzL!Md_ z&=7>TWciblF@EMC3t4-WX{?!m!G6$M$1S?NzF*2KHMP3Go4=#ZHkeIv{eEd;s-yD# z_jU^Ba06TZqvV|Yd;Z_sN%$X=!T+&?#p+OQIHS%!LO`Hx0q_Y0MyGYFNoM{W;&@0@ zLM^!X4KhdtsET5G<0+|q0oqVXMW~-7LW9Bg}=E$YtNh1#1D^6Mz(V9?2g~I1( zoz9Cz=8Hw98zVLwC2AQvp@pBeKyidn6Xu0-1SY1((^Hu*-!HxFUPs)yJ+i`^BC>PC zjwd0mygOVK#d2pRC9LxqGc6;Ui>f{YW9Bvb>33bp^NcnZoH~w9(lM5@JiIlfa-6|k ziy31UoMN%fvQfhi8^T+=yrP{QEyb-jK~>$A4SZT-N56NYEbpvO&yUme&pWKs3^94D zH{oXnUTb3T@H+RgzML*lejx`WAyw*?K7B-I(VJx($2!NXYm%3`=F~TbLv3H<{>D?A zJo-FDYdSA-(Y%;4KUP2SpHKAIcv9-ld(UEJE7=TKp|Gryn;72?0LHqAN^fk6%8PCW z{g_-t)G5uCIf0I`*F0ZNl)Z>))MaLMpXgqWgj-y;R+@A+AzDjsTqw2Mo9ULKA3c70 z!7SOkMtZb+MStH>9MnvNV0G;pwSW9HgP+`tg}e{ij0H6Zt5zJ7iw`hEnvye!XbA@!~#%vIkzowCOvq5I5@$3wtc*w2R$7!$*?}vg4;eDyJ_1=ixJuEp3pUS27W?qq(P^8$_lU!mRChT}ctvZz4p!X^ zOSp|JOAi~f?UkwH#9k{0smZ7-#=lK6X3OFEMl7%)WIcHb=#ZN$L=aD`#DZKOG4p4r zwlQ~XDZ`R-RbF&hZZhu3(67kggsM-F4Y_tI^PH8PMJRcs7NS9ogF+?bZB*fcpJ z=LTM4W=N9yepVvTj&Hu~0?*vR1HgtEvf8w%Q;U0^`2@e8{SwgX5d(cQ|1(!|i$km! zvY03MK}j`sff;*-%mN~ST>xU$6Bu?*Hm%l@0dk;j@%>}jsgDcQ)Hn*UfuThz9(ww_ zasV`rSrp_^bp-0sx>i35FzJwA!d6cZ5#5#nr@GcPEjNnFHIrtUYm1^Z$;{d&{hQV9 z6EfFHaIS}46p^5I-D_EcwwzUUuO}mqRh&T7r9sfw`)G^Q%oHxEs~+XoM?8e*{-&!7 z7$m$lg9t9KP9282eke608^Q2E%H-xm|oJ8=*SyEo} z@&;TQ3K)jgspgKHyGiKVMCz>xmC=H5Fy3!=TP)-R3|&1S-B)!6q50wfLHKM@7Bq6E z44CY%G;GY>tC`~yh!qv~YdXw! zSkquvYNs6k1r7>Eza?Vkkxo6XRS$W7EzL&A`o>=$HXgBp{L(i^$}t`NcnAxzbH8Ht z2!;`bhKIh`f1hIFcI5bHI=ueKdzmB9)!z$s-BT4ItyY|NaA_+o=jO%MU5as9 zc2)aLP>N%u>wlaXTK!p)r?+~)L+0eCGb5{8WIk7K52$nufnQ+m8YF+GQc&{^(zh-$ z#wyWV*Zh@d!b(WwXqvfhQX)^aoHTBkc;4ossV3&Ut*k>AI|m+{#kh4B!`3*<)EJVj zwrxK>99v^k4&Y&`Awm>|exo}NvewV%E+@vOc>5>%H#BK9uaE2$vje zWYM5fKuOTtn96B_2~~!xJPIcXF>E_;yO8AwpJ4)V`Hht#wbO3Ung~@c%%=FX4)q+9 z99#>VC2!4l`~0WHs9FI$Nz+abUq# zz`Of97})Su=^rGp2S$)7N3rQCj#0%2YO<R&p>$<#lgXcUj=4H_{oAYiT3 z44*xDn-$wEzRw7#@6aD)EGO$0{!C5Z^7#yl1o;k0PhN=aVUQu~eTQ^Xy{z8Ow6tk83 z4{5xe%(hx)%nD&|e*6sTWH`4W&U!Jae#U4TnICheJmsw{l|CH?UA{a6?2GNgpZLyzU2UlFu1ZVwlALmh_DOs03J^Cjh1im`E3?9&zvNmg(MuMw&0^Lu$(#CJ*q6DjlKsY-RMJ^8yIY|{SQZ*9~CH|u9L z`R78^r=EbbR*_>5?-)I+$6i}G)%mN(`!X72KaV(MNUP7Nv3MS9S|Pe!%N2AeOt5zG zVJ;jI4HZ$W->Ai_4X+`9c(~m=@ek*m`ZQbv3ryI-AD#AH=`x$~WeW~M{Js57(K7(v ze5`};LG|%C_tmd>bkufMWmAo&B+DT9ZV~h(4jg0>^aeAqL`PEUzJJtI8W1M!bQWpv zvN(d}E1@nlYa!L!!A*RN!(Q3F%J?5PvQ0udu?q-T)j3JKV~NL>KRb~w-lWc685uS6 z=S#aR&B8Sc8>cGJ!!--?kwsJTUUm`Jk?7`H z7PrO~xgBrSW2_tTlCq1LH8*!o?pj?qxy8}(=r_;G18POrFh#;buWR0qU24+XUaVZ0 z?(sXcr@-YqvkCmHr{U2oPogHL{r#3r49TeR<{SJX1pcUqyWPrkYz^X8#QW~?F)R5i z>p^!i<;qM8Nf{-fd6!_&V*e_9qP6q(s<--&1Ttj01j0w>bXY7y1W*%Auu&p|XSOH=)V7Bd4fUKh&T1)@cvqhuD-d=?w}O zjI%i(f|thk0Go*!d7D%0^ztBfE*V=(ZIN84f5HU}T9?ulmEYzT5usi=DeuI*d|;M~ zp_=Cx^!4k#=m_qSPBr5EK~E?3J{dWWPH&oCcNepYVqL?nh4D5ynfWip$m*YlZ8r^Z zuFEUL-nW!3qjRCLIWPT0x)FDL7>Yt7@8dA?R2kF@WE>ysMY+)lTsgNM#3VbXVGL}F z1O(>q>2a+_`6r5Xv$NZAnp=Kgnr3)cL(^=8ypEeOf3q8(HGe@7Tt59;yFl||w|mnO zHDxg2G3z8=(6wjj9kbcEY@Z0iOd7Gq5GiPS5% z*sF1J<#daxDV2Z8H>wxOF<;yKzMeTaSOp_|XkS9Sfn6Mpe9UBi1cSTieGG5$O;ZLIIJ60Y>SN4vC?=yE_CWlo(EEE$e4j?z&^FM%kNmRtlbEL^dPPgvs9sbK5fGw*r@ z+!EU@u$T8!nZh?Fdf_qk$VuHk^yVw`h`_#KoS*N%epIIOfQUy_&V}VWDGp3tplMbf z5Se1sJUC$7N0F1-9jdV2mmGK{-}fu|Nv;12jDy0<-kf^AmkDnu6j~TPWOgy1MT68|D z=4=50jVbUKdKaQgD`eWGr3I&^<6uhkjz$YwItY8%Yp9{z4-{6g{73<_b*@XJ4Nm3-3z z?BW3{aY_ccRjb@W1)i5nLg|7BnWS!B`_Uo9CWaE`Ij327QH?i)9A}4Ug4wmxVVa^b z-4+m%-wwOl7cKH7+=x&nrCrbEC)Q$fpg&V83#uEH;C=GNMz`ps@^RxK%T*8%OPnC` z{WO~J%nxYJ`x|N%?&i7?;{_8t^jM&=50HlaOQj8fS}_`moH$c;vI<|cruPFnpT8yU zS%rPOCUSd5Zdb(zwk`hqwTQn)*&n)uYsP*F_(~xEWq}C= zv30kFmZFwJZ@ELVX3?$dXQh|icO7UrL*_5G=I^xXjImz`ZPp>?g#tf(ej~KaIU0algsG!IS09;>?MvqGg#c{i+}qY|{P8W~O%#>|gFd z<1dr$-oxyRGN17yZo1OwLnzwYs0|;IS_nymNB0IlSzPQ%-r`?T=;_XQ^~&#}b|AB} zkNbN5uB?-sUB-T5QLlg%Uk3)uHB;>VIzGe9_J9 zaeISkQm!v(9d(0ML^b9fR^sfHFlH?7Mvddt37OuR{|O0{uv)(&-6<87W4 zyO>s!=cPgP3O&7xxU5DlIPw_o3O>6o6Qb?JWs3qw#p3sBc3g$?Dx zi(6D+DYgV;GrUis-CL%Qe{nvZnwaVXmbhH(|GFh|Q)k=1uvA$I@1DXI7bKlQ@8D6P zS?(*?><>)G49q0wr;NajpxP4W2G)kHl6^=Z>hrNEI4Mwd_$O6$1dXF;Q#hE(-eeW6 zz03GJF%Wl?HO=_ztv5*zRlcU~{+{k%#N59mgm~eK>P!QZ6E?#Cu^2)+K8m@ySvZ*5 z|HDT}BkF@3!l(0%75G=1u2hETXEj!^1Z$!)!lyGXlWD!_vqGE$Z)#cUVBqlORW>0^ zDjyVTxwKHKG|0}j-`;!R-p>}qQfBl(?($7pP<+Y8QE#M8SCDq~k<+>Q^Zf@cT_WdX3~BSe z+|KK|7OL5Hm5(NFP~j>Ct3*$wi0n0!xl=(C61`q&cec@mFlH(sy%+RH<=s)8aAPN`SfJdkAQjdv82G5iRdv8 zh{9wHUZaniSEpslXl^_ODh}mypC?b*9FzLjb~H@3DFSe;D(A-K3t3eOTB(m~I6C;(-lKAvit(70k`%@+O*Ztdz;}|_TS~B?Tpmi=QKC^m_ z2YpEaT3iiz*;T~ap1yiA)a`dKMwu`^UhIUeltNQ1Yjo=q@bI@&3zH?rVUg=IxLy-ni zyxDu%-Fr{H6owTjZU2O5>nDb=q&Jz_TjeSq%!2m40x&U6w~GQ({quPL73IsJS;f`$ zsuhioqCBj(gJ>2hoo)Gou7(WP*pX)f=Y=!=k!&1K?EYY%jJ~X&DnK{^saPQK<1BJ z_A`_{%ZozcB(3w$z^To^6d|XuT@=X~wtW!+{4ID@N{AB~J6AL5vuY>JwvWCNFKsKh zd}@>q@_WV#QZ&UJ0#?X(pXR!oyXOEG3rqzHbCzGLONDb042i$})fM@XF)uSP(DHUc z^&{|$*xe{cs?Gp8=B%RY3L7#$ve$?TWh>MZdxF1zH1v}1z+$Ov#G7?%D)bBCyDe*% zSeKSpETC2V1){II>@UwJi>4uBN+iAx+82E~gb|Cr&8E^i&)A!uv-g?jzH99wU}8+# z$nh>yvb;TwZmS@7LrvuCu_d0-WxFNI&C7%sWuTL%YU!l|I1{|->=dlOeHOCtUO#zkS3ESO8LHV4hTdQL5EdV zuWD33fFPH}HPrW^s$Qn1Xgp&AT6<-He{{4%eIu3rN=iK|9mURdKXfB&Q?qGok%!cs ze53UP{Z!TO-Y@q2;;k2avA3`lm4OoN4@S*k=UA)7H;qZ`d8`XaYFCv?Ba+uGW@r5v z&&{nf(24WSBOhc7!qF^@0cz;XcUynNaj6w2349;s!K{KVqs5yS{ z7VubS`2OzT^5#1~6Tt^RTvt9-J|D2F>y~>2;jeF>g`hx5l%B3H=aLExQihuYngzlnBTYOTHJQMzl>kwqN5JYs)Ej zblA@ntkUS~xi+}y6|(81helS}Q~&VB37qyV|S3Y=><^1wh%msQM?fz z<58MX(=|PSUKCF#)dbhR%D&xgCD?$aR0qen+wpp6 zst}vX18!Be96TD??j1HsHTUx(a&@F?=gT`Q$oJFFyrh^;zgz!(NlAHGn0cJy@us=w zNhC#l5G;H}+>49Nsh12=ZPO2r*2OBQe5kpb&1?*PIBFitK8}FUfb~S-#hKfF0o#&d z#3aPkB$9scYku&kA6{0xHnBV#&Wei5J>5T-XX-gUXEPo+9b7WL=*XESc(3BshL`aj zXp}QIp*40}oWJt*l043e8_5;H5PI5c)U&IEw5dF(4zjX0y_lk9 zAp@!mK>WUqHo)-jop=DoK>&no>kAD=^qIE7qis&_*4~ z6q^EF$D@R~3_xseCG>Ikb6Gfofb$g|75PPyyZN&tiRxqovo_k zO|HA|sgy#B<32gyU9x^&)H$1jvw@qp+1b(eGAb)O%O!&pyX@^nQd^9BQ4{(F8<}|A zhF&)xusQhtoXOOhic=8#Xtt5&slLia3c*a?dIeczyTbC#>FTfiLST57nc3@Y#v_Eg#VUv zT8cKH#f3=1PNj!Oroz_MAR*pow%Y0*6YCYmUy^7`^r|j23Q~^*TW#cU7CHf0eAD_0 zEWEVddxFgQ7=!nEBQ|ibaScslvhuUk^*%b#QUNrEB{3PG@uTxNwW}Bs4$nS9wc(~O zG7Iq>aMsYkcr!9#A;HNsJrwTDYkK8ikdj{M;N$sN6BqJ<8~z>T20{J8Z2rRUuH7~3 z=tgS`AgxbBOMg87UT4Lwge`*Y=01Dvk>)^{Iu+n6fuVX4%}>?3czOGR$0 zpp*wp>bsFFSV`V;r_m+TZns$ZprIi`OUMhe^cLE$2O+pP3nP!YB$ry}2THx2QJs3< za1;>d-AggCarrQ>&Z!d@;mW+!q6eXhb&`GbzUDSxpl8AJ#Cm#tuc)_xh(2NV=5XMs zrf_ozRYO$NkC=pKFX5OH8v1>0i9Z$ec`~Mf+_jQ68spn(CJwclDhEEkH2Qw;${J$clv__nUjn5jA0wCLEnu1j;v!0vB>Ri6m9`;R{JMS%^)4FC zU0Z44+u$I$w=Bj|iu4DT5h~sS`C*zbmX?@-crY}E+hy>}2~C0Nn(EKk@5^qO4@l@! z6O0lr%tzGC`D^)8xU3FnMZVm0kX1sBWhaQyzVoXFWwr%Ny?=2M{5s#5i7fTu3gEkG zc{(Pr$v=;`Y#&`y*J}#M9ux>0?xu!`$9cUKm#Bdd_&S#LPTS?ZPV6zN6>W6JTS~-LfjL{mB=b(KMk3 z2HjBSlJeyUVqDd=Mt!=hpYsvby2GL&3~zm;0{^nZJq+4vb?5HH4wufvr}IX42sHeK zm@x?HN$8TsTavXs)tLDFJtY9b)y~Tl@7z4^I8oUQq4JckH@~CVQ;FoK(+e0XAM>1O z(ei}h?)JQp>)d=6ng-BZF1Z5hsAKW@mXq+hU?r8I(*%`tnIIOXw7V6ZK(T9RFJJe@ zZS!aC+p)Gf2Ujc=a6hx4!A1Th%YH!Lb^xpI!Eu` zmJO{9rw){B1Ql18d%F%da+Tbu1()?o(zT7StYqK6_w`e+fjXq5L^y(0 z09QA6H4oFj59c2wR~{~>jUoDzDdKz}5#onYPJRwa`SUO)Pd4)?(ENBaFVLJr6Kvz= zhTtXqbx09C1z~~iZt;g^9_2nCZ{};-b4dQJbv8HsWHXPVg^@(*!@xycp#R?a|L!+` zY5w))JWV`Gls(=}shH0#r*;~>_+-P5Qc978+QUd>J%`fyn{*TsiG-dWMiJXNgwBaT zJ=wgYFt+1ACW)XwtNx)Q9tA2LPoB&DkL16P)ERWQlY4%Y`-5aM9mZ{eKPUgI!~J3Z zkMd5A_p&v?V-o-6TUa8BndiX?ooviev(DKw=*bBVOW|=zps9=Yl|-R5@yJe*BPzN}a0mUsLn{4LfjB_oxpv(mwq# zSY*%E{iB)sNvWfzg-B!R!|+x(Q|b@>{-~cFvdDHA{F2sFGA5QGiIWy#3?P2JIpPKg6ncI^)dvqe`_|N=8G$eqJB ziT4y8-Y@s9rkH0Tz>ll(^xkcTl)CY?rS&9VNd66Yc)g^6)JcWaY(5$5gt z8gr3SBXUTN;~cBgz&})qX%#!Fxom2Yau_`&8)+6aSN7YY+pS410rRUU*>J}qL0TnJ zRxt*7QeUqTh8j)Q&iavh<}L+$Jqz))<`IfKussVk%%Ah-Ti?Eo0hQH!rK%K=#EAw0 zwq@@~XNUXRnv8$;zv<6rCRJ6fPD^hfrh;0K?n z=p!u^3xOgWZ%f3+?+>H)9+w^$Tn1e;?UpVMJb!!;f)`6f&4|8mr+g)^@x>_rvnL0< zvD0Hu_N>$(Li7|Jgu0mRh&MV+<}`~Wi*+avM01E)Jtg=)-vViQKax!GeDc!xv$^mL z{#OVBA$U{(Zr8~Xm|cP@odkHC*1R8z6hcLY#N@3E-A8XEvpt066+3t9L_6Zg6j@9Q zj$$%~yO-OS6PUVrM2s)(T4#6=JpI_@Uz+!6=GdyVU?`!F=d;8#ZB@(5g7$A0(`eqY z8_i@3w$0*es5mrSjhW*qzrl!_LQWs4?VfLmo1Sd@Ztt53+etwzAT^8ow_*7Jp`Y|l z*UgSEwvxq+FYO!O*aLf-PinZYne7Ib6ny3u>MjQz=((r3NTEeU4=-i0LBq3H-VJH< z^>1RE3_JwrclUn9vb7HcGUaFRA0QHcnE;6)hnkp%lY1UII#WPAv?-;c?YH}LWB8Nl z{sx-@Z;QxWh9fX8SxLZk8;kMFlGD3Jc^QZVL4nO)1I$zQwvwM&_!kW+LMf&lApv#< zur|EyC|U@5OQuph$TC_ZU`{!vJp`13e9alaR0Dbn5ikLFH7>eIz4QbV|C=%7)F=qo z_>M&5N)d)7G(A%c>}UCrW!Ql_6_A{?R7&CL`;!KOb3 z8Z=$YkV-IF;c7zs{3-WDEFJzuakFbd*4LWd<_kBE8~BFcv}js_2OowRNzWCtCQ6&k z{&~Me92$m*@e0ANcWKuz)?YjB*VoSTx??-3Cc0l2U!X^;Bv@m87eKHukAljrD54R+ zE;@_w4NPe1>3`i5Qy*3^E9x#VB6?}v=~qIprrrd5|DFkg;v5ixo0IsBmik8=Y;zv2 z%Bcf%NE$a44bk^`i4VwDLTbX=q@j9;JWT9JncQ!+Y%2&HHk@1~*L8-{ZpY?(-a9J-1~<1ltr9i~D9`P{XTIFWA6IG8c4;6bFw*lzU-{+?b&%OcIoCiw00n>A1ra zFPE$y@>ebbZlf(sN_iWBzQKDV zmmaLX#zK!@ZdvCANfwV}9@2O&w)!5gSgQzHdk2Q`jG6KD7S+1R5&F)j6QTD^=hq&7 zHUW+r^da^%V(h(wonR(j?BOiC!;y=%nJvz?*aW&5E87qq;2z`EI(f zBJNNSMFF9U{sR-af5{IY&AtoGcoG)Iq-S^v{7+t0>7N(KRoPj;+2N5;9o_nxIGjJ@ z7bYQK)bX)vEhy~VL%N6g^NE@D5VtV+Q8U2%{ji_=6+i^G%xeskEhH>Sqr194PJ$fB zu1y^){?9Vkg(FY2h)3ZHrw0Z<@;(gd_dtF#6y_;Iwi{yX$?asr?0N0_B*CifEi7<6 zq`?OdQjCYbhVcg+7MSgIM|pJRu~`g?g3x?Tl+V}#$It`iD1j+!x+!;wS0+2e>#g?Z z*EA^k7W{jO1r^K~cD#5pamp+o@8&yw6;%b|uiT?{Wa=4+9<}aXWUuL#ZwN1a;lQod zW{pxWCYGXdEq9qAmvAB904}?97=re$>!I%wxPV#|f#@A*Y=qa%zHlDv^yWbR03%V0 zprLP+b(#fBqxI%FiF*-n8HtH6$8f(P6!H3V^ysgd8de-N(@|K!A< z^qP}jp(RaM9kQ(^K(U8O84?D)aU(g?1S8iWwe)gqpHCaFlJxb*ilr{KTnu4_@5{K- z)n=CCeCrPHO0WHz)dDtkbZfUfVBd?53}K>C5*-wC4hpDN8cGk3lu-ypq+EYpb_2H; z%vP4@&+c2p;thaTs$dc^1CDGlPG@A;yGR5@$UEqk6p58qpw#7lc<+W(WR;(vr(D>W z#(K$vE#uBkT=*q&uaZwzz=P5mjiee6>!lV?c}QIX%ZdkO1dHg>Fa#xcGT6~}1*2m9 zkc7l3ItD6Ie~o_aFjI$Ri=C!8uF4!Ky7iG9QTrxVbsQroi|r)SAon#*B*{}TB-?=@ z8~jJs;_R2iDd!$+n$%X6FO&PYS{YhDAS+U2o4su9x~1+U3z7YN5o0qUK&|g^klZ6X zj_vrM5SUTnz5`*}Hyts9ADwLu#x_L=nv$Z0`HqN`Zo=V>OQI)fh01n~*a%01%cx%0 z4LTFVjmW+ipVQv5rYcn3;d2o4qunWUY!p+?s~X~(ost@WR@r@EuDOSs8*MT4fiP>! zkfo^!PWJJ1MHgKS2D_hc?Bs?isSDO61>ebl$U*9*QY(b=i&rp3@3GV@z>KzcZOxip z^dzA~44;R~cnhWz7s$$v?_8y-k!DZys}Q?4IkSyR!)C0j$(Gm|t#e3|QAOFaV2}36 z?dPNY;@I=FaCwylc_;~kXlZsk$_eLkNb~TIl8QQ`mmH&$*zwwR8zHU*sId)rxHu*K z;yZWa8UmCwju%aSNLwD5fBl^b0Ux1%q8YR*uG`53Mi<`5uA^Dc6Ync)J3N7;zQ*75)hf%a@{$H+%S?SGT)ks60)?6j$ zspl|4Ad6@%-r1t*$tT(en!gIXTUDcsj?28ZEzz)dH)SV3bZ+pjMaW0oc~rOPZP@g! zb9E+ndeVO_Ib9c_>{)`01^`ZS198 z)(t=+{Azi11$eu%aU7jbwuQrO`vLOixuh~%4z@mKr_Oc;F%Uq01fA)^W&y+g16e?rkLhTxV!EqC%2}sx_1u7IBq|}Be&7WI z4I<;1-9tJsI&pQIhj>FPkQV9{(m!wYYV@i5h?A0#BN2wqlEwNDIq06|^2oYVa7<~h zI_OLan0Do*4R5P=a3H9`s5*>xU}_PSztg`+2mv)|3nIy=5#Z$%+@tZnr> zLcTI!Mxa`PY7%{;KW~!=;*t)R_sl<^b>eNO@w#fEt(tPMg_jpJpW$q_DoUlkY|uo> z0-1{ouA#;t%spf*7VjkK&$QrvwUERKt^Sdo)5@?qAP)>}Y!h4(JQ!7{wIdkA+|)bv z&8hBwoX4v|+fie}iTslaBX^i*TjwO}f{V)8*!dMmRPi%XAWc8<_IqK1jUsApk)+~R zNFTCD-h>M5Y{qTQ&0#j@I@tmXGj%rzhTW5%Bkh&sSc=$Fv;M@1y!zvYG5P2(2|(&W zlcbR1{--rJ&s!rB{G-sX5^PaM@3EqWVz_y9cwLR9xMig&9gq(voeI)W&{d6j1jh&< zARXi&APWE1FQWh7eoZjuP z;vdgX>zep^{{2%hem;e*gDJhK1Hj12nBLIJoL<=0+8SVEBx7!4Ea+hBY;A1gBwvY<)tj~T=H`^?3>zeWWm|LAwo*S4Z%bDVUe z6r)CH1H!(>OH#MXFJ2V(U(qxD{4Px2`8qfFLG+=a;B^~Te_Z!r3RO%Oc#ZAHKQxV5 zRYXxZ9T2A%NVJIu5Pu7!Mj>t%YDO$T@M=RR(~mi%sv(YXVl`yMLD;+WZ{vG9(@P#e zMo}ZiK^7^h6TV%cG+;jhJ0s>h&VERs=tuZz^Tlu~%d{ZHtq6hX$V9h)Bw|jVCMudd zwZ5l7In8NT)qEPGF$VSKg&fb0%R2RnUnqa){)V(X(s0U zkCdVZe6wy{+_WhZh3qLp245Y2RR$@g-!9PjJ&4~0cFSHMUn=>dapv)hy}|y91ZWTV zCh=z*!S3_?`$&-eZ6xIXUq8RGl9oK0BJw*TdU6A`LJqX9eS3X@F)g$jLkBWFscPhR zpCv8#KeAc^y>>Y$k^=r|K(DTC}T$0#jQBOwB#@`P6~*IuW_8JxCG}J4va{ zsZzt}tt+cv7=l&CEuVtjD6G2~_Meh%p4RGuY?hSt?(sreO_F}8r7Kp$qQdvCdZnDQ zxzc*qchE*E2=WK)^oRNa>Ttj`fpvF-JZ5tu5>X1xw)J@1!IqWjq)ESBG?J|ez`-Tc zi5a}GZx|w-h%5lNDE_3ho0hEXMoaofo#Z;$8|2;EDF&*L+e$u}K=u?pb;dv$SXeQM zD-~7P0i_`Wk$#YP$=hw3UVU+=^@Kuy$>6?~gIXx636jh{PHly_a2xNYe1l60`|y!7 z(u%;ILuW0DDJ)2%y`Zc~hOALnj1~txJtcdD#o4BCT68+8gZe`=^te6H_egxY#nZH&P*)hgYaoJ^qtmpeea`35Fw)cy!w@c#v6E29co8&D9CTCl%^GV|X;SpneSXzV~LXyRn-@K0Df z{tK-nDWA!q38M1~`xUIt_(MO^R(yNY#9@es9RQbY@Ia*xHhD&=k^T+ zJi@j2I|WcgW=PuAc>hs`(&CvgjL2a9Rx zCbZyUpi8NWUOi@S%t+Su4|r&UoU|ze9SVe7p@f1GBkrjkkq)T}X%Qo1g!SQ{O{P?m z-OfGyyWta+UCXH+-+(D^%kw#A1-U;?9129at7MeCCzC{DNgO zeSqsV>W^NIfTO~4({c}KUiuoH8A*J!Cb0*sp*w-Bg@YfBIPZFH!M}C=S=S7PLLcIG zs7K77g~W)~^|+mx9onzMm0qh(f~OsDTzVmRtz=aZTllgR zGUn~_5hw_k&rll<4G=G+`^Xlnw;jNYDJz@bE?|r866F2hA9v0-8=JO3g}IHB#b`hy zA42a0>{0L7CcabSD+F7?pGbS1KMvT{@1_@k!_+Ki|5~EMGt7T%u=79F)8xEiL5!EJ zzuxQ`NBliCoJMJdwu|);zRCD<5Sf?Y>U$trQ-;xj6!s5&w=9E7)%pZ+1Nh&8nCCwM zv5>Ket%I?cxr3vVva`YeR?dGxbG@pi{H#8@kFEf0Jq6~K4>kt26*bxv=P&jyE#e$| zDJB_~imk^-z|o!2njF2hL*|7sHCnzluhJjwLQGDmC)Y9 zr9ZN`s)uCd^XDvn)VirMgW~qfn1~SaN^7vcX#K1G`==UGaDVVx$0BQnubhX|{e z^i0}>k-;BP#Szk{cFjO{2x~LjK{^Upqd&<+03_iMLp0$!6_$@TbX>8U-f*-w-ew1?`CtD_0y_Lo|PfKi52p?`5$Jzx0E8`M0 zNIb?#!K$mM4X%`Ry_yhG5k@*+n4||2!~*+&pYLh~{`~o(W|o64^NrjP?-1Lgu?iK^ zTX6u3?#$?R?N!{599vg>G8RGHw)Hx&=|g4599y}mXNpM{EPKKXB&+m?==R3GsIq?G zL5fH={=zawB(sMlDBJ+{dgb)Vx3pu>L=mDV0{r1Qs{0Pn%TpopH{m(By4;{FBvi{I z$}x!Iw~MJOL~&)p93SDIfP3x%ROjg}X{Sme#hiJ&Yk&a;iR}V|n%PriZBY8SX2*;6 z4hdb^&h;Xz%)BDACY5AUsV!($lib4>11UmcgXKWpzRL8r2Srl*9Y(1uBQsY&hO&uv znDNff0tpHlLISam?o(lOp#CmFdH<6HmA0{UwfU#Y{8M+7od8b8|B|7ZYR9f<#+V|ZSaCQvI$~es~g(Pv{2&m_rKSB2QQ zMvT}$?Ll>V+!9Xh5^iy3?UG;dF-zh~RL#++roOCsW^cZ&({6q|?Jt6`?S8=16Y{oH zp50I7r1AC1(#{b`Aq5cw>ypNggHKM9vBx!W$eYIzD!4KbLsZGr2o8>g<@inmS3*>J zx8oG((8f!ei|M@JZB`p7+n<Q}?>h249<`7xJ?u}_n;Gq(&km#1ULN87CeTO~FY zS_Ty}0TgQhV zOh3T7{{x&LSYGQfKR1PDIkP!WnfC1$l+fs@Di+d4O=eVKeF~2fq#1<8hEvpwuqcaH z4A8u~r^gnY3u6}zj*RHjk{AHhrrDqaj?|6GaVJbV%o-nATw}ASFr!f`Oz|u_QPkR# z0mDudY1dZRlk@TyQ?%Eti=$_WNFtLpSx9=S^be{wXINp%MU?a`F66LNU<c;0&ngifmP9i;bj6&hdGMW^Kf8e6ZDXbQD&$QAAMo;OQ)G zW(qlHh;}!ZP)JKEjm$VZjTs@hk&4{?@+NADuYrr!R^cJzU{kGc1yB?;7mIyAWwhbeA_l_lw-iDVi7wcFurf5 z#Uw)A@a9fOf{D}AWE%<`s1L_AwpZ?F!Vac$LYkp<#A!!`XKaDC{A%)~K#5z6>Hv@V zBEqF(D5?@6r3Pwj$^krpPDCjB+UOszqUS;b2n>&iAFcw<*im2(b3|5u6SK!n9Sg4I z0KLcwA6{Mq?p%t>aW0W!PQ>iUeYvNjdKYqII!CE7SsS&Rj)eIw-K4jtI?II+0IdGq z2WT|L3RL?;GtGgt1LWfI4Ka`9dbZXc$TMJ~8#Juv@K^1RJN@yzdLS8$AJ(>g!U9`# zx}qr7JWlU+&m)VG*Se;rGisutS%!6yybi%B`bv|9rjS(xOUIvbNz5qtvC$_JYY+c& za*3*2$RUH8p%pSq>48xR)4qsp!Q7BEiJ*`^>^6INRbC@>+2q9?x(h0bpc>GaNFi$K zPH$6!#(~{8@0QZk=)QnM#I=bDx5vTvjm$f4K}%*s+((H2>tUTf==$wqyoI`oxI7>C z&>5fe)Yg)SmT)eA(|j@JYR1M%KixxC-Eceknf-;N=jJTwKvk#@|J^&5H0c+%KxHUI z6dQbwwVx3p?X<_VRVb2fStH?HH zFR@Mp=qX%#L3XL)+$PXKV|o|#DpHAoqvj6uQKe@M-mnhCSou7Dj4YuO6^*V`m)1lf z;)@e%1!Qg$10w8uEmz{ENb$^%u}B;J7sDd zump}onoD#!l=agcBR)iG!3AF0-63%@`K9G(CzKrm$VJ{v7^O9Ps7Zej|3m= zVXlR&yW6=Y%mD30G@|tf=yC7-#L!16Q=dq&@beWgaIL40k0n% z)QHrp2Jck#evLMM1RGt3WvQ936ZC9vEje0nFMfvmOHVI+&okB_K|l-;|4vW;qk>n~ z+|kk8#`K?x`q>`(f6A${wfw9Cx(^)~tX7<#TpxR#zYG2P+FY~mG{tnEkv~d6oUQA+ z&hNTL=~Y@rF`v-RZlts$nb$3(OL1&@Y11hhL9+zUb6)SP!;CD)^GUtUpCHBE`j1te zAGud@miCVFLk$fjsrcpjsadP__yj9iEZUW{Ll7PPi<$R;m1o!&Xdl~R_v0;oDX2z^!&8}zNGA}iYG|k zmehMd1%?R)u6R#<)B)1oe9TgYH5-CqUT8N7K-A-dm3hbm_W21p%8)H{O)xUlBVb+iUR}-v5dFaCyfSd zC6Bd7=N4A@+Bna=!-l|*_(nWGDpoyU>nH=}IOrLfS+-d40&(Wo*dDB9nQiA2Tse$R z;uq{`X7LLzP)%Y9aHa4YQ%H?htkWd3Owv&UYbr5NUDAH^<l@Z0Cx%`N+B*i!!1u>D8%;Qt1$ zE5O0{-`9gdDxZ!`0m}ywH!;c{oBfL-(BH<&SQ~smbcobU!j49O^f4&IIYh~f+hK*M zZwTp%{ZSAhMFj1qFaOA+3)p^gnXH^=)`NTYgTu!CLpEV2NF=~-`(}7p^Eof=@VUbd z_9U|8qF7Rueg&$qpSSkN%%%DpbV?8E8ivu@ensI0toJ7Eas^jyFReQ1JeY9plb^{m z&eQO)qPLZQ6O;FTr*aJq=$cMN)QlQO@G&%z?BKUs1&I^`lq>=QLODwa`(mFGC`0H< zOlc*|N?B5&!U6BuJvkL?s1&nsi$*5cCv7^j_*l&$-sBmRS85UIrE--7eD8Gr3^+o? zqG-Yl4S&E;>H>k^a0GdUI(|n1`ws@)1%sq2XBdK`mqrNq_b4N{#VpouCXLzNvjoFv zo9wMQ6l0+FT+?%N(ka*;%m~(?338bu32v26!{r)|w8J`EL|t$}TA4q_FJRX5 zCPa{hc_I(7TGE#@rO-(!$1H3N-C0{R$J=yPCXCtGk{4>=*B56JdXU9cQVwB`6~cQZ zf^qK21x_d>X%dT!!)CJQ3mlHA@ z{Prkgfs6=Tz%63$6Zr8CO0Ak3A)Cv#@BVKr&aiKG7RYxY$Yx>Bj#3gJk*~Ps-jc1l z;4nltQwwT4@Z)}Pb!3xM?+EW0qEKA)sqzw~!C6wd^{03-9aGf3Jmt=}w-*!yXupLf z;)>-7uvWN4Unn8b4kfIza-X=x*e4n5pU`HtgpFFd))s$C@#d>aUl3helLom+RYb&g zI7A9GXLRZPl}iQS*d$Azxg-VgcUr*lpLnbPKUV{QI|bsG{8bLG<%CF( zMoS4pRDtLVYOWG^@ox^h8xL~afW_9DcE#^1eEC1SVSb1BfDi^@g?#f6e%v~Aw>@w- zIY0k+2lGWNV|aA*e#`U3=+oBDmGeInfcL)>*!w|*;mWiKNG6wP6AW4-4imN!W)!hE zA02~S1*@Q`fD*+qX@f3!2yJX&6FsEfPditB%TWo3=HA;T3o2IrjS@9SSxv%{{7&4_ zdS#r4OU41~GYMiib#z#O;zohNbhJknrPPZS6sN$%HB=jUnlCO_w5Gw5EeE@KV>soy z2EZ?Y|4RQDDjt5y!WBlZ(8M)|HP<0YyG|D%RqD+K#e7-##o3IZxS^wQ5{Kbzb6h(i z#(wZ|^ei>8`%ta*!2tJzwMv+IFHLF`zTU8E^Mu!R*45_=ccqI};Zbyxw@U%a#2}%f zF>q?SrUa_a4H9l+uW8JHh2Oob>NyUwG=QH~-^ZebU*R@67DcXdz2{HVB4#@edz?B< z5!rQH3O0>A&ylROO%G^fimV*LX7>!%re{_Sm6N>S{+GW1LCnGImHRoF@csnFzn@P0 zM=jld0z%oz;j=>c7mMwzq$B^2mae7NiG}%>(wtmsDXkWk{?BeMpTrIt3Mizq?vRsf zi_WjNp+61uV(%gEU-Vf0;>~vcDhe(dzWdaf#4mH3o^v{0EWhj?E?$5v02sV@xL0l4 zX0_IMFtQ44PfWBbPYN#}qxa%=J%dlR{O!KyZvk^g5s?sTNycWYPJ^FK(nl3k?z-5t z39#hKrdO7V(@!TU)LAPY&ngnZ1MzLEeEiZznn7e-jLCy8LO zu^7_#z*%I-BjS#Pg-;zKWWqX-+Ly$T!4`vTe5ZOV0j?TJVA*2?*=82^GVlZIuH%9s zXiV&(T(QGHHah=s&7e|6y?g+XxZGmK55`wGV>@1U)Th&=JTgJq>4mI&Av2C z)w+kRoj_dA!;SfTfkgMPO>7Dw6&1*Hi1q?54Yng`JO&q->^CX21^PrU^JU#CJ_qhV zSG>afB%>2fx<~g8p=P8Yzxqc}s@>>{g7}F!;lCXvF#RV)^fyYb_)iKVCz1xEq=fJ| z0a7DMCK*FuP=NM*5h;*D`R4y$6cpW-E&-i{v`x=Jbk_xSn@2T3q!3HoAOB`@5Vg6) z{PW|@9o!e;v1jZ2{=Uw6S6o{g82x6g=k!)cFSC*oemHaVjg?VpEmtUuD2_J^A~$4* z3O7HsbA6wxw{TP5Kk)(Vm?gKo+_}11vbo{Tp_5x79P~#F)ahQXT)tSH5;;14?s)On zel1J>1x>+7;g1Iz2FRpnYz;sD0wG9Q!vuzE9yKi3@4a9Nh1!GGN?hA)!mZEnnHh&i zf?#ZEN2sFbf~kV;>K3UNj1&vFhc^sxgj8FCL4v>EOYL?2uuT`0eDH}R zmtUJMxVrV5H{L53hu3#qaWLUa#5zY?f5ozIn|PkMWNP%n zWB5!B0LZB0kLw$k39=!akkE9Q>F4j+q434jB4VmslQ;$ zKiO#FZ`p|dKS716jpcvR{QJkSNfDVhr2%~eHrW;fU45>>snr*S8Vik-5eN5k*c2Mp zyxvX&_cFbB6lODXznHHT|rsURe2!swomtrqc~w5 zymTM8!w`1{04CBprR!_F{5LB+2_SOuZN{b*!J~1ZiPpP-M;);!ce!rOPDLtgR@Ie1 zPreuqm4!H)hYePcW1WZ0Fyaqe%l}F~Orr)~+;mkS&pOhP5Ebb`cnUt!X_QhP4_4p( z8YKQCDKGIy>?WIFm3-}Br2-N`T&FOi?t)$hjphB9wOhBXU#Hb+zm&We_-O)s(wc`2 z8?VsvU;J>Ju7n}uUb3s1yPx_F*|FlAi=Ge=-kN?1;`~6szP%$3B0|8Sqp%ebM)F8v zADFrbeT0cgE>M0DMV@_Ze*GHM>q}wWMzt|GYC%}r{OXRG3Ij&<+nx9;4jE${Fj_r* z`{z1AW_6Myd)i6e0E-h&m{{CvzH=Xg!&(bLYgRMO_YVd8JU7W+7MuGWNE=4@OvP9+ zxi^vqS@5%+#gf*Z@RVyU9N1sO-(rY$24LGsg1>w>s6ST^@)|D9>cT50maXLUD{Fzf zt~tp{OSTEKg3ZSQyQQ5r51){%=?xlZ54*t1;Ow)zLe3i?8tD8YyY^k%M)e`V*r+vL zPqUf&m)U+zxps+NprxMHF{QSxv}>lE{JZETNk1&F+R~bp{_T$dbXL2UGnB|hgh*p4h$clt#6;NO~>zuyY@C-MD@)JCc5XrYOt`wW7! z_ti2hhZBMJNbn0O-uTxl_b6Hm313^fG@e;RrhIUK9@# z+DHGv_Ow$%S8D%RB}`doJjJy*aOa5mGHVHz0e0>>O_%+^56?IkA5eN+L1BVCp4~m=1eeL zb;#G!#^5G%6Mw}r1KnaKsLvJB%HZL)!3OxT{k$Yo-XrJ?|7{s4!H+S2o?N|^Z z)+?IE9H7h~Vxn5hTis^3wHYuOU84+bWd)cUKuHapq=&}WV#OxHpLab`NpwHm8LmOo zjri+!k;7j_?FP##CpM+pOVx*0wExEex z@`#)K<-ZrGyArK;a%Km`^+We|eT+#MygHOT6lXBmz`8|lyZOwL1+b+?Z$0OhMEp3R z&J=iRERpv~TC=p2-BYLC*?4 zxvPs9V@g=JT0>zky5Poj=fW_M!c)Xxz1<=&_ZcL=LMZJqlnO1P^xwGGW*Z+yTBvbV z-IFe6;(k1@$1;tS>{%pXZ_7w+i?N4A2=TXnGf=YhePg8bH8M|Lk-->+w8Y+FjZ;L=wSGwxfA`gqSn)f(XNuSm>6Y z@|#e-)I(PQ^G@N`%|_DZSb4_pkaEF0!-nqY+t#pyA>{9^*I-zw4SYA1_z2Bs$XGUZbGA;VeMo%CezHK0lO={L%G)dI-+8w?r9iexdoB{?l zbJ}C?huIhWXBVs7oo{!$lOTlvCLZ_KN1N+XJGuG$rh<^eUQIqcI7^pmqhBSaOKNRq zrx~w^?9C?*&rNwP_SPYmo;J-#!G|{`$JZK7DxsM3N^8iR4vvn>E4MU&Oe1DKJvLc~ zCT>KLZ1;t@My zRj_2hI^61T&LIz)S!+AQIV23n1>ng+LUvzv;xu!4;wpqb#EZz;F)BLUzT;8UA1x*6vJ zicB!3Mj03s*kGV{g`fpC?V^s(=JG-k1EMHbkdP4P*1^8p_TqO|;!Zr%GuP$8KLxuf z=pv*H;kzd;P|2`JmBt~h6|GxdU~@weK5O=X&5~w$HpfO}@l-T7@vTCxVOwCkoPQv8 z@aV_)I5HQtfs7^X=C03zYmH4m0S!V@JINm6#(JmZRHBD?T!m^DdiZJrhKpBcur2u1 zf9e4%k$$vcFopK5!CC`;ww(CKL~}mlxK_Pv!cOsFgVkNIghA2Au@)t6;Y3*2gK=5d z?|@1a)-(sQ%uFOmJ7v2iG&l&m^u&^6DJM#XzCrF%r>{2XKyxLD2rgWBD;i(!e4InDQBDg==^z;AzT2z~OmV0!?Z z0S9pX$+E;w3WN;v&NYT=+G8hf=6w0E1$0AOr61}eOvE8W1jX%>&Mjo7&!ulawgzLH zbcb+IF(s^3aj12WSi#pzIpijJJzkP?JzRawnxmNDSUR#7!29vHULCE<3Aa#be}ie~d|!V+ z%l~s9Odo$G&fH!t!+`rUT0T9DulF!Yq&BfQWFZV1L9D($r4H(}Gnf6k3^wa7g5|Ws zj7%d`!3(0bb55yhC6@Q{?H|2os{_F%o=;-h{@Yyyn*V7?{s%Grvpe!H^kl6tF4Zf5 z{Jv1~yZ*iIWL_9C*8pBMQArfJJ0d9Df6Kl#wa}7Xa#Ef_5B7=X}DzbQXVPfCwTO@9+@;A^Ti6il_C>g?A-GFwA0#U;t4;wOm-4oS})h z5&on>NAu67O?YCQr%7XIzY%LS4bha9*e*4bU4{lGCUmO2UQ2U)QOqClLo61Kx~3dI zmV3*(P6F_Tr-oP%x!0kTnnT?Ep5j;_IQ^pTRp=e8dmJtI4YgWd0}+b2=ATkOhgpXe z;jmw+FBLE}UIs4!&HflFr4)vMFOJ19W4f2^W(=2)F%TAL)+=F>IE$=e=@j-*bFLSg z)wf|uFQu+!=N-UzSef62u0-C8Zc7 zo6@F)c+nZA{H|+~7i$DCU0pL{0Ye|fKLuV^w!0Y^tT$isu%i1Iw&N|tX3kwFKJN(M zXS`k9js66o$r)x?TWL}Kxl`wUDUpwFx(w4Yk%49;$sgVvT~n8AgfG~HUcDt1TRo^s zdla@6heJB@JV z!vK;BUMznhzGK6PVtj0)GB=zTv6)Q9Yt@l#fv7>wKovLobMV-+(8)NJmyF8R zcB|_K7=FJGGn^X@JdFaat0uhKjp3>k#^&xE_}6NYNG?kgTp>2Iu?ElUjt4~E-?`Du z?mDCS9wbuS%fU?5BU@Ijx>1HG*N?gIP+<~xE4u=>H`8o((cS5M6@_OK%jSjFHirQK zN9@~NXFx*jS{<|bgSpC|SAnA@I)+GB=2W|JJChLI_mx+-J(mSJ!b)uUom6nH0#2^(L@JBlV#t zLl?j54s`Y3vE^c_3^Hl0TGu*tw_n?@HyO@ZrENxA+^!)OvUX28gDSF*xFtQzM$A+O zCG=n#6~r|3zt=8%GuG} z<#VCZ%2?3Q(Ad#Y7GMJ~{U3>E{5e@z6+rgZLX{Cxk^p-7dip^d29;2N1_mm4QkASo z-L`GWWPCq$uCo;X_BmGIpJFBlhl<8~EG{vOD1o|X$aB9KPhWO_cKiU*$HWEgtf=fn zsO%9bp~D2c@?*K9jVN@_vhR03>M_8h!_~%aN!Cnr?s-!;U3SVfmhRwk11A^8Ns`@KeE}+ zN$H}a1U6E;*j5&~Og!xHdfK5M<~xka)x-0N)K_&e7AjMz`toDzasH+^1bZlC!n()crk9kg@$(Y{wdKvbuUd04N^8}t1iOgsKF zGa%%XWx@WoVaNC1!|&{5ZbkopFre-Lu(LCE5HWZBoE#W@er9W<>R=^oYxBvypN#x3 zq#LC8&q)GFP=5^-bpHj?LW=)-g+3_)Ylps!3^YQ{9~O9&K)xgy zMkCWaApU-MI~e^cV{Je75Qr7eF%&_H)BvfyKL=gIA>;OSq(y z052BFz3E(Prg~09>|_Z@!qj}@;8yxnw+#Ej0?Rk<y}4ghbD569B{9hSFr*^ygZ zr6j7P#gtZh6tMk6?4V$*Jgz+#&ug;yOr>=qdI#9U&^am2qoh4Jy}H2%a|#Fs{E(5r z%!ijh;VuGA6)W)cJZx+;9Bp1LMUzN~x_8lQ#D3+sL{be-Jyeo@@dv7XguJ&S5vrH` z>QxOMWn7N-T!D@1(@4>ZlL^y5>m#0!HKovs12GRav4z!>p(1~xok8+_{| z#Ae4{9#NLh#Vj2&JuIn5$d6t@__`o}umFo(n0QxUtd2GKCyE+erwXY?`cm*h&^9*8 zJ+8x6fRZI-e$CRygofIQN^dWysCxgkyr{(_oBwwSRxZora1(%(aC!5BTtj^+YuevI zx?)H#(xlALUp6QJ!=l9N__$cxBZ5p&7;qD3PsXRFVd<({Kh+mShFWJNpy`N@ab7?9 zv5=klvCJ4bx|-pvOO2-+G)6O?$&)ncA#Urze2rlBfp#htudhx-NeRnJ@u%^_bfw4o z4|{b8SkPV3b>Wera1W(+N@p9H>dc6{cnkh-sgr?e%(YkWvK+0YXVwk0=d`)}*47*B z5JGkEdVix!w7-<%r0JF~`ZMMPe;f0EQHuYHxya`puazyph*ZSb1mJAt^k4549BfS; zK7~T&lRb=W{s&t`DJ$B}s-eH1&&-wEOH1KWsKn0a(ZI+G!v&W4A*cl>qAvUv6pbUR z#(f#EKV8~hk&8oayBz4vaswc(?qw1vn`yC zZQDl2PCB-&Uu@g9ZQHhO+v(W0bNig{-k0;;`+wM@#@J)8r?qOYs#&vUna8ILxN7S{ zp1s41KnR8miQJtJtOr|+qk}wrLt+N*z#5o`TmD1)E&QD(Vh&pjZJ_J*0!8dy_ z>^=@v=J)C`x&gjqAYu`}t^S=DFCtc0MkBU2zf|69?xW`Ck~(6zLD)gSE{7n~6w8j_ zoH&~$ED2k5-yRa0!r8fMRy z;QjBYUaUnpd}mf%iVFPR%Dg9!d>g`01m~>2s))`W|5!kc+_&Y>wD@@C9%>-lE`WB0 zOIf%FVD^cj#2hCkFgi-fgzIfOi+ya)MZK@IZhHT5FVEaSbv-oDDs0W)pA0&^nM0TW zmgJmd7b1R7b0a`UwWJYZXp4AJPteYLH>@M|xZFKwm!t3D3&q~av?i)WvAKHE{RqpD{{%OhYkK?47}+}` zrR2(Iv9bhVa;cDzJ%6ntcSbx7v7J@Y4x&+eWSKZ*eR7_=CVIUSB$^lfYe@g+p|LD{ zPSpQmxx@b$%d!05|H}WzBT4_cq?@~dvy<7s&QWtieJ9)hd4)$SZz}#H2UTi$CkFWW|I)v_-NjuH!VypONC=1`A=rm_jfzQ8Fu~1r8i{q-+S_j$ z#u^t&Xnfi5tZtl@^!fUJhx@~Cg0*vXMK}D{>|$#T*+mj(J_@c{jXBF|rm4-8%Z2o! z2z0o(4%8KljCm^>6HDK!{jI7p+RAPcty_~GZ~R_+=+UzZ0qzOwD=;YeZt*?3%UGdr z`c|BPE;yUbnyARUl&XWSNJ<+uRt%!xPF&K;(l$^JcA_CMH6)FZt{>6ah$|(9$2fc~ z=CD00uHM{qv;{Zk9FR0~u|3|Eiqv9?z2#^GqylT5>6JNZwKqKBzzQpKU2_pmtD;CT zi%Ktau!Y2Tldfu&b0UgmF(SSBID)15*r08eoUe#bT_K-G4VecJL2Pa=6D1K6({zj6 za(2Z{r!FY5W^y{qZ}08+h9f>EKd&PN90f}Sc0ejf%kB4+f#T8Q1=Pj=~#pi$U zp#5rMR%W25>k?<$;$x72pkLibu1N|jX4cWjD3q^Pk3js!uK6h7!dlvw24crL|MZs_ zb%Y%?Fyp0bY0HkG^XyS76Ts*|Giw{31LR~+WU5NejqfPr73Rp!xQ1mLgq@mdWncLy z%8}|nzS4P&`^;zAR-&nm5f;D-%yNQPwq4N7&yULM8bkttkD)hVU>h>t47`{8?n2&4 zjEfL}UEagLUYwdx0sB2QXGeRmL?sZ%J!XM`$@ODc2!y|2#7hys=b$LrGbvvjx`Iqi z&RDDm3YBrlKhl`O@%%&rhLWZ*ABFz2nHu7k~3@e4)kO3%$=?GEFUcCF=6-1n!x^vmu+Ai*amgXH+Rknl6U>#9w;A} zn2xanZSDu`4%%x}+~FG{Wbi1jo@wqBc5(5Xl~d0KW(^Iu(U3>WB@-(&vn_PJt9{1`e9Iic@+{VPc`vP776L*viP{wYB2Iff8hB%E3|o zGMOu)tJX!`qJ}ZPzq7>=`*9TmETN7xwU;^AmFZ-ckZjV5B2T09pYliaqGFY|X#E-8 z20b>y?(r-Fn5*WZ-GsK}4WM>@TTqsxvSYWL6>18q8Q`~JO1{vLND2wg@58OaU!EvT z1|o+f1mVXz2EKAbL!Q=QWQKDZpV|jznuJ}@-)1&cdo z^&~b4Mx{*1gurlH;Vhk5g_cM&6LOHS2 zRkLfO#HabR1JD4Vc2t828dCUG#DL}f5QDSBg?o)IYYi@_xVwR2w_ntlpAW0NWk$F1 z$If?*lP&Ka1oWfl!)1c3fl`g*lMW3JOn#)R1+tfwrs`aiFUgz3;XIJ>{QFxLCkK30 zNS-)#DON3yb!7LBHQJ$)4y%TN82DC2-9tOIqzhZ27@WY^<6}vXCWcR5iN{LN8{0u9 zNXayqD=G|e?O^*ms*4P?G%o@J1tN9_76e}E#66mr89%W_&w4n66~R;X_vWD(oArwj z4CpY`)_mH2FvDuxgT+akffhX0b_slJJ*?Jn3O3~moqu2Fs1oL*>7m=oVek2bnprnW zixkaIFU%+3XhNA@@9hyhFwqsH2bM|`P?G>i<-gy>NflhrN{$9?LZ1ynSE_Mj0rADF zhOz4FnK}wpLmQuV zgO4_Oz9GBu_NN>cPLA=`SP^$gxAnj;WjJnBi%Q1zg`*^cG;Q)#3Gv@c^j6L{arv>- zAW%8WrSAVY1sj$=umcAf#ZgC8UGZGoamK}hR7j6}i8#np8ruUlvgQ$j+AQglFsQQq zOjyHf22pxh9+h#n$21&$h?2uq0>C9P?P=Juw0|;oE~c$H{#RGfa>| zj)Iv&uOnaf@foiBJ}_;zyPHcZt1U~nOcNB{)og8Btv+;f@PIT*xz$x!G?u0Di$lo7 zOugtQ$Wx|C($fyJTZE1JvR~i7LP{ zbdIwqYghQAJi9p}V&$=*2Azev$6K@pyblphgpv8^9bN!?V}{BkC!o#bl&AP!3DAjM zmWFsvn2fKWCfjcAQmE+=c3Y7j@#7|{;;0f~PIodmq*;W9Fiak|gil6$w3%b_Pr6K_ zJEG@&!J%DgBZJDCMn^7mk`JV0&l07Bt`1ymM|;a)MOWz*bh2#d{i?SDe9IcHs7 zjCrnyQ*Y5GzIt}>`bD91o#~5H?4_nckAgotN{2%!?wsSl|LVmJht$uhGa+HiH>;av z8c?mcMYM7;mvWr6noUR{)gE!=i7cZUY7e;HXa221KkRoc2UB>s$Y(k%NzTSEr>W(u z<(4mcc)4rB_&bPzX*1?*ra%VF}P1nwiP5cykJ&W{!OTlz&Td0pOkVp+wc z@k=-Hg=()hNg=Q!Ub%`BONH{ z_=ZFgetj@)NvppAK2>8r!KAgi>#%*7;O-o9MOOfQjV-n@BX6;Xw;I`%HBkk20v`qoVd0)}L6_49y1IhR z_OS}+eto}OPVRn*?UHC{eGyFU7JkPz!+gX4P>?h3QOwGS63fv4D1*no^6PveUeE5% zlehjv_3_^j^C({a2&RSoVlOn71D8WwMu9@Nb@=E_>1R*ve3`#TF(NA0?d9IR_tm=P zOP-x;gS*vtyE1Cm zG0L?2nRUFj#aLr-R1fX*$sXhad)~xdA*=hF3zPZhha<2O$Ps+F07w*3#MTe?)T8|A!P!v+a|ot{|^$q(TX`35O{WI0RbU zCj?hgOv=Z)xV?F`@HKI11IKtT^ocP78cqHU!YS@cHI@{fPD?YXL)?sD~9thOAv4JM|K8OlQhPXgnevF=F7GKD2#sZW*d za}ma31wLm81IZxX(W#A9mBvLZr|PoLnP>S4BhpK8{YV_}C|p<)4#yO{#ISbco92^3 zv&kCE(q9Wi;9%7>>PQ!zSkM%qqqLZW7O`VXvcj;WcJ`2~v?ZTYB@$Q&^CTfvy?1r^ z;Cdi+PTtmQwHX_7Kz?r#1>D zS5lWU(Mw_$B&`ZPmqxpIvK<~fbXq?x20k1~9az-Q!uR78mCgRj*eQ>zh3c$W}>^+w^dIr-u{@s30J=)1zF8?Wn|H`GS<=>Om|DjzC{}Jt?{!fSJe*@$H zg>wFnlT)k#T?LslW zu$^7Uy~$SQ21cE?3Ijl+bLfuH^U5P^$@~*UY#|_`uvAIe(+wD2eF}z_y!pvomuVO; zS^9fbdv)pcm-B@CW|Upm<7s|0+$@@<&*>$a{aW+oJ%f+VMO<#wa)7n|JL5egEgoBv zl$BY(NQjE0#*nv=!kMnp&{2Le#30b)Ql2e!VkPLK*+{jv77H7)xG7&=aPHL7LK9ER z5lfHxBI5O{-3S?GU4X6$yVk>lFn;ApnwZybdC-GAvaznGW-lScIls-P?Km2mF>%B2 zkcrXTk+__hj-3f48U%|jX9*|Ps41U_cd>2QW81Lz9}%`mTDIhE)jYI$q$ma7Y-`>% z8=u+Oftgcj%~TU}3nP8&h7k+}$D-CCgS~wtWvM|UU77r^pUw3YCV80Ou*+bH0!mf0 zxzUq4ed6y>oYFz7+l18PGGzhB^pqSt)si=9M>~0(Bx9*5r~W7sa#w+_1TSj3Jn9mW zMuG9BxN=}4645Cpa#SVKjFst;9UUY@O<|wpnZk$kE+to^4!?0@?Cwr3(>!NjYbu?x z1!U-?0_O?k!NdM^-rIQ8p)%?M+2xkhltt*|l=%z2WFJhme7*2xD~@zk#`dQR$6Lmd zb3LOD4fdt$Cq>?1<%&Y^wTWX=eHQ49Xl_lFUA(YQYHGHhd}@!VpYHHm=(1-O=yfK#kKe|2Xc*9}?BDFN zD7FJM-AjVi)T~OG)hpSWqH>vlb41V#^G2B_EvYlWhDB{Z;Q9-0)ja(O+By`31=biA zG&Fs#5!%_mHi|E4Nm$;vVQ!*>=_F;ZC=1DTPB#CICS5fL2T3XmzyHu?bI;m7D4@#; ztr~;dGYwb?m^VebuULtS4lkC_7>KCS)F@)0OdxZIFZp@FM_pHnJes8YOvwB|++#G( z&dm*OP^cz95Wi15vh`Q+yB>R{8zqEhz5of>Po$9LNE{xS<)lg2*roP*sQ}3r3t<}; zPbDl{lk{pox~2(XY5=qg0z!W-x^PJ`VVtz$git7?)!h>`91&&hESZy1KCJ2nS^yMH z!=Q$eTyRi68rKxdDsdt+%J_&lapa{ds^HV9Ngp^YDvtq&-Xp}60B_w@Ma>_1TTC;^ zpbe!#gH}#fFLkNo#|`jcn?5LeUYto%==XBk6Ik0kc4$6Z+L3x^4=M6OI1=z5u#M%0 z0E`kevJEpJjvvN>+g`?gtnbo$@p4VumliZV3Z%CfXXB&wPS^5C+7of2tyVkMwNWBiTE2 z8CdPu3i{*vR-I(NY5syRR}I1TJOV@DJy-Xmvxn^IInF>Tx2e)eE9jVSz69$6T`M9-&om!T+I znia!ZWJRB28o_srWlAxtz4VVft8)cYloIoVF=pL zugnk@vFLXQ_^7;%hn9x;Vq?lzg7%CQR^c#S)Oc-8d=q_!2ZVH764V z!wDKSgP}BrVV6SfCLZnYe-7f;igDs9t+K*rbMAKsp9L$Kh<6Z;e7;xxced zn=FGY<}CUz31a2G}$Q(`_r~75PzM4l_({Hg&b@d8&jC}B?2<+ed`f#qMEWi z`gm!STV9E4sLaQX+sp5Nu9*;9g12naf5?=P9p@H@f}dxYprH+3ju)uDFt^V{G0APn zS;16Dk{*fm6&BCg#2vo?7cbkkI4R`S9SSEJ=#KBk3rl69SxnCnS#{*$!^T9UUmO#&XXKjHKBqLdt^3yVvu8yn|{ zZ#%1CP)8t-PAz(+_g?xyq;C2<9<5Yy<~C74Iw(y>uUL$+$mp(DRcCWbCKiGCZw@?_ zdomfp+C5xt;j5L@VfhF*xvZdXwA5pcdsG>G<8II-|1dhAgzS&KArcb0BD4ZZ#WfiEY{hkCq5%z9@f|!EwTm;UEjKJsUo696V>h zy##eXYX}GUu%t{Gql8vVZKkNhQeQ4C%n|RmxL4ee5$cgwlU+?V7a?(jI#&3wid+Kz5+x^G!bb#$q>QpR#BZ}Xo5UW^ zD&I`;?(a}Oys7-`I^|AkN?{XLZNa{@27Dv^s4pGowuyhHuXc zuctKG2x0{WCvg_sGN^n9myJ}&FXyGmUQnW7fR$=bj$AHR88-q$D!*8MNB{YvTTEyS zn22f@WMdvg5~o_2wkjItJN@?mDZ9UUlat2zCh(zVE=dGi$rjXF7&}*sxac^%HFD`Y zTM5D3u5x**{bW!68DL1A!s&$2XG@ytB~dX-?BF9U@XZABO`a|LM1X3HWCllgl0+uL z04S*PX$%|^WAq%jkzp~%9HyYIF{Ym?k)j3nMwPZ=hlCg9!G+t>tf0o|J2%t1 ztC+`((dUplgm3`+0JN~}&FRRJ3?l*>Y&TfjS>!ShS`*MwO{WIbAZR#<%M|4c4^dY8 z{Rh;-!qhY=dz5JthbWoovLY~jNaw>%tS4gHVlt5epV8ekXm#==Po$)}mh^u*cE>q7*kvX&gq)(AHoItMYH6^s6f(deNw%}1=7O~bTHSj1rm2|Cq+3M z93djjdomWCTCYu!3Slx2bZVy#CWDozNedIHbqa|otsUl+ut?>a;}OqPfQA05Yim_2 zs@^BjPoFHOYNc6VbNaR5QZfSMh2S*`BGwcHMM(1@w{-4jVqE8Eu0Bi%d!E*^Rj?cR z7qgxkINXZR)K^=fh{pc0DCKtrydVbVILI>@Y0!Jm>x-xM!gu%dehm?cC6ok_msDVA*J#{75%4IZt}X|tIVPReZS#aCvuHkZxc zHVMtUhT(wp09+w9j9eRqz~LtuSNi2rQx_QgQ(}jBt7NqyT&ma61ldD(s9x%@q~PQl zp6N*?=N$BtvjQ_xIT{+vhb1>{pM0Arde0!X-y))A4znDrVx8yrP3B1(7bKPE5jR@5 zwpzwT4cu~_qUG#zYMZ_!2Tkl9zP>M%cy>9Y(@&VoB84#%>amTAH{(hL4cDYt!^{8L z645F>BWO6QaFJ-{C-i|-d%j7#&7)$X7pv#%9J6da#9FB5KyDhkA+~)G0^87!^}AP>XaCSScr;kL;Z%RSPD2CgoJ;gpYT5&6NUK$86$T?jRH=w8nI9Z534O?5fk{kd z`(-t$8W|#$3>xoMfXvV^-A(Q~$8SKDE^!T;J+rQXP71XZ(kCCbP%bAQ1|%$%Ov9_a zyC`QP3uPvFoBqr_+$HenHklqyIr>PU_Fk5$2C+0eYy^~7U&(!B&&P2%7#mBUhM!z> z_B$Ko?{Pf6?)gpYs~N*y%-3!1>o-4;@1Zz9VQHh)j5U1aL-Hyu@1d?X;jtDBNk*vMXPn@ z+u@wxHN*{uHR!*g*4Xo&w;5A+=Pf9w#PeZ^x@UD?iQ&${K2c}UQgLRik-rKM#Y5rdDphdcNTF~cCX&9ViRP}`>L)QA4zNXeG)KXFzSDa6 zd^St;inY6J_i=5mcGTx4_^Ys`M3l%Q==f>{8S1LEHn{y(kbxn5g1ezt4CELqy)~TV6{;VW>O9?5^ ztcoxHRa0jQY7>wwHWcxA-BCwzsP>63Kt&3fy*n#Cha687CQurXaRQnf5wc9o8v7Rw zNwGr2fac;Wr-Ldehn7tF^(-gPJwPt@VR1f;AmKgxN&YPL;j=0^xKM{!wuU|^mh3NE zy35quf}MeL!PU;|{OW_x$TBothLylT-J>_x6p}B_jW1L>k)ps6n%7Rh z96mPkJIM0QFNYUM2H}YF5bs%@Chs6#pEnloQhEl?J-)es!(SoJpEPoMTdgA14-#mC zghayD-DJWtUu`TD8?4mR)w5E`^EHbsz2EjH5aQLYRcF{l7_Q5?CEEvzDo(zjh|BKg z3aJl_n#j&eFHsUw4~lxqnr!6NL*se)6H=A+T1e3xUJGQrd}oSPwSy5+$tt{2t5J5@(lFxl43amsARG74iyNC}uuS zd2$=(r6RdamdGx^eatX@F2D8?U23tDpR+Os?0Gq2&^dF+$9wiWf?=mDWfjo4LfRwL zI#SRV9iSz>XCSgEj!cW&9H-njJopYiYuq|2w<5R2!nZ27DyvU4UDrHpoNQZiGPkp@ z1$h4H46Zn~eqdj$pWrv;*t!rTYTfZ1_bdkZmVVIRC21YeU$iS-*XMNK`#p8Z_DJx| zk3Jssf^XP7v0X?MWFO{rACltn$^~q(M9rMYoVxG$15N;nP)A98k^m3CJx8>6}NrUd@wp-E#$Q0uUDQT5GoiK_R{ z<{`g;8s>UFLpbga#DAf%qbfi`WN1J@6IA~R!YBT}qp%V-j!ybkR{uY0X|x)gmzE0J z&)=eHPjBxJvrZSOmt|)hC+kIMI;qgOnuL3mbNR0g^<%|>9x7>{}>a2qYSZAGPt4it?8 zNcLc!Gy0>$jaU?}ZWxK78hbhzE+etM`67*-*x4DN>1_&{@5t7_c*n(qz>&K{Y?10s zXsw2&nQev#SUSd|D8w7ZD2>E<%g^; zV{yE_O}gq?Q|zL|jdqB^zcx7vo(^})QW?QKacx$yR zhG|XH|8$vDZNIfuxr-sYFR{^csEI*IM#_gd;9*C+SysUFejP0{{z7@P?1+&_o6=7V|EJLQun^XEMS)w(=@eMi5&bbH*a0f;iC~2J74V2DZIlLUHD&>mlug5+v z6xBN~8-ovZylyH&gG#ptYsNlT?-tzOh%V#Y33zlsJ{AIju`CjIgf$@gr8}JugRq^c zAVQ3;&uGaVlVw}SUSWnTkH_6DISN&k2QLMBe9YU=sA+WiX@z)FoSYX`^k@B!j;ZeC zf&**P?HQG6Rk98hZ*ozn6iS-dG}V>jQhb3?4NJB*2F?6N7Nd;EOOo;xR7acylLaLy z9)^lykX39d@8@I~iEVar4jmjjLWhR0d=EB@%I;FZM$rykBNN~jf>#WbH4U{MqhhF6 zU??@fSO~4EbU4MaeQ_UXQcFyO*Rae|VAPLYMJEU`Q_Q_%s2*>$#S^)&7er+&`9L=1 z4q4ao07Z2Vsa%(nP!kJ590YmvrWg+YrgXYs_lv&B5EcoD`%uL79WyYA$0>>qi6ov7 z%`ia~J^_l{p39EY zv>>b}Qs8vxsu&WcXEt8B#FD%L%ZpcVtY!rqVTHe;$p9rbb5O{^rFMB>auLn-^;s+-&P1#h~mf~YLg$8M9 zZ4#87;e-Y6x6QO<{McUzhy(%*6| z)`D~A(TJ$>+0H+mct(jfgL4x%^oC^T#u(bL)`E2tBI#V1kSikAWmOOYrO~#-cc_8! zCe|@1&mN2{*ceeiBldHCdrURk4>V}79_*TVP3aCyV*5n@jiNbOm+~EQ_}1#->_tI@ zqXv+jj2#8xJtW508rzFrYcJxoek@iW6SR@1%a%Bux&;>25%`j3UI`0DaUr7l79`B1 zqqUARhW1^h6=)6?;@v>xrZNM;t}{yY3P@|L}ey@gG( z9r{}WoYN(9TW&dE2dEJIXkyHA4&pU6ki=rx&l2{DLGbVmg4%3Dlfvn!GB>EVaY_%3+Df{fBiqJV>~Xf8A0aqUjgpa} zoF8YXO&^_x*Ej}nw-$-F@(ddB>%RWoPUj?p8U{t0=n>gAI83y<9Ce@Q#3&(soJ{64 z37@Vij1}5fmzAuIUnXX`EYe;!H-yTVTmhAy;y8VZeB#vD{vw9~P#DiFiKQ|kWwGFZ z=jK;JX*A;Jr{#x?n8XUOLS;C%f|zj-7vXtlf_DtP7bpurBeX%Hjwr z4lI-2TdFpzkjgiv!8Vfv`=SP+s=^i3+N~1ELNWUbH|ytVu>EyPN_3(4TM^QE1swRo zoV7Y_g)a>28+hZG0e7g%@2^s>pzR4^fzR-El}ARTmtu!zjZLuX%>#OoU3}|rFjJg} zQ2TmaygxJ#sbHVyiA5KE+yH0LREWr%^C*yR|@gM$nK2P zo}M}PV0v))uJh&33N>#aU376@ZH79u(Yw`EQ2hM3SJs9f99+cO6_pNW$j$L-CtAfe zYfM)ccwD!P%LiBk!eCD?fHCGvgMQ%Q2oT_gmf?OY=A>&PaZQOq4eT=lwbaf}33LCH zFD|)lu{K7$8n9gX#w4~URjZxWm@wlH%oL#G|I~Fb-v^0L0TWu+`B+ZG!yII)w05DU z>GO?n(TN+B=>HdxVDSlIH76pta$_LhbBg;eZ`M7OGcqt||qi zogS72W1IN%=)5JCyOHWoFP7pOFK0L*OAh=i%&VW&4^LF@R;+K)t^S!96?}^+5QBIs zjJNTCh)?)4k^H^g1&jc>gysM`y^8Rm3qsvkr$9AeWwYpa$b22=yAd1t<*{ zaowSEFP+{y?Ob}8&cwfqoy4Pb9IA~VnM3u!trIK$&&0Op#Ql4j>(EW?UNUv#*iH1$ z^j>+W{afcd`{e&`-A{g}{JnIzYib)!T56IT@YEs{4|`sMpW3c8@UCoIJv`XsAw!XC z34|Il$LpW}CIHFC5e*)}00I5{%OL*WZRGzC0?_}-9{#ue?-ug^ zLE|uv-~6xnSs_2_&CN9{9vyc!Xgtn36_g^wI0C4s0s^;8+p?|mm;Odt3`2ZjwtK;l zfd6j)*Fr#53>C6Y8(N5?$H0ma;BCF3HCjUs7rpb2Kf*x3Xcj#O8mvs#&33i+McX zQpBxD8!O{5Y8D&0*QjD=Yhl9%M0)&_vk}bmN_Ud^BPN;H=U^bn&(csl-pkA+GyY0Z zKV7sU_4n;}uR78ouo8O%g*V;79KY?3d>k6%gpcmQsKk&@Vkw9yna_3asGt`0Hmj59 z%0yiF*`jXhByBI9QsD=+>big5{)BGe&+U2gAARGe3ID)xrid~QN_{I>k}@tzL!Md_ z&=7>TWciblF@EMC3t4-WX{?!m!G6$M$1S?NzF*2KHMP3Go4=#ZHkeIv{eEd;s-yD# z_jU^Ba06TZqvV|Yd;Z_sN%$X=!T+&?#p+OQIHS%!LO`Hx0q_Y0MyGYFNoM{W;&@0@ zLM^!X4KhdtsET5G<0+|q0oqVXMW~-7LW9Bg}=E$YtNh1#1D^6Mz(V9?2g~I1( zoz9Cz=8Hw98zVLwC2AQvp@pBeKyidn6Xu0-1SY1((^Hu*-!HxFUPs)yJ+i`^BC>PC zjwd0mygOVK#d2pRC9LxqGc6;Ui>f{YW9Bvb>33bp^NcnZoH~w9(lM5@JiIlfa-6|k ziy31UoMN%fvQfhi8^T+=yrP{QEyb-jK~>$A4SZT-N56NYEbpvO&yUme&pWKs3^94D zH{oXnUTb3T@H+RgzML*lejx`WAyw*?K7B-I(VJx($2!NXYm%3`=F~TbLv3H<{>D?A zJo-FDYdSA-(Y%;4KUP2SpHKAIcv9-ld(UEJE7=TKp|Gryn;72?0LHqAN^fk6%8PCW z{g_-t)G5uCIf0I`*F0ZNl)Z>))MaLMpXgqWgj-y;R+@A+AzDjsTqw2Mo9ULKA3c70 z!7SOkMtZb+MStH>9MnvNV0G;pwSW9HgP+`tg}e{ij0H6Zt5zJ7iw`hEnvye!XbA@!~#%vIkzowCOvq5I5@$3wtc*w2R$7!$*?}vg4;eDyJ_1=ixJuEp3pUS27W?qq(P^8$_lU!mRChT}ctvZz4p!X^ zOSp|JOAi~f?UkwH#9k{0smZ7-#=lK6X3OFEMl7%)WIcHb=#ZN$L=aD`#DZKOG4p4r zwlQ~XDZ`R-RbF&hZZhu3(67kggsM-F4Y_tI^PH8PMJRcs7NS9ogF+?bZB*fcpJ z=LTM4W=N9yepVvTj&Hu~0?*vR1HgtEvf8w%Q;U0^`2@e8{SwgX5d(cQ|1(!|i$km! zvY03MK}j`sff;*-%mN~ST>xU$6Bu?*Hm%l@0dk;j@%>}jsgDcQ)Hn*UfuThz9(ww_ zasV`rSrp_^bp-0sx>i35FzJwA!d6cZ5#5#nr@GcPEjNnFHIrtUYm1^Z$;{d&{hQV9 z6EfFHaIS}46p^5I-D_EcwwzUUuO}mqRh&T7r9sfw`)G^Q%oHxEs~+XoM?8e*{-&!7 z7$m$lg9t9KP9282eke608^Q2E%H-xm|oJ8=*SyEo} z@&;TQ3K)jgspgKHyGiKVMCz>xmC=H5Fy3!=TP)-R3|&1S-B)!6q50wfLHKM@7Bq6E z44CY%G;GY>tC`~yh!qv~YdXw! zSkquvYNs6k1r7>Eza?Vkkxo6XRS$W7EzL&A`o>=$HXgBp{L(i^$}t`NcnAxzbH8Ht z2!;`bhKIh`f1hIFcI5bHI=ueKdzmB9)!z$s-BT4ItyY|NaA_+o=jO%MU5as9 zc2)aLP>N%u>wlaXTK!p)r?+~)L+0eCGb5{8WIk7K52$nufnQ+m8YF+GQc&{^(zh-$ z#wyWV*Zh@d!b(WwXqvfhQX)^aoHTBkc;4ossV3&Ut*k>AI|m+{#kh4B!`3*<)EJVj zwrxK>99v^k4&Y&`Awm>|exo}NvewV%E+@vOc>5>%H#BK9uaE2$vje zWYM5fKuOTtn96B_2~~!xJPIcXF>E_;yO8AwpJ4)V`Hht#wbO3Ung~@c%%=FX4)q+9 z99#>VC2!4l`~0WHs9FI$Nz+abUq# zz`Of97})Su=^rGp2S$)7N3rQCj#0%2YO<R&p>$<#lgXcUj=4H_{oAYiT3 z44*xDn-$wEzRw7#@6aD)EGO$0{!C5Z^7#yl1o;k0PhN=aVUQu~eTQ^Xy{z8Ow6tk83 z4{5xe%(hx)%nD&|e*6sTWH`4W&U!Jae#U4TnICheJmsw{l|CH?UA{a6?2GNgpZLyzU2UlFu1ZVwlALmh_DOs03J^Cjh1im`E3?9&zvNmg(MuMw&0^Lu$(#CJ*q6DjlKsY-RMJ^8yIY|{SQZ*9~CH|u9L z`R78^r=EbbR*_>5?-)I+$6i}G)%mN(`!X72KaV(MNUP7Nv3MS9S|Pe!%N2AeOt5zG zVJ;jI4HZ$W->Ai_4X+`9c(~m=@ek*m`ZQbv3ryI-AD#AH=`x$~WeW~M{Js57(K7(v ze5`};LG|%C_tmd>bkufMWmAo&B+DT9ZV~h(4jg0>^aeAqL`PEUzJJtI8W1M!bQWpv zvN(d}E1@nlYa!L!!A*RN!(Q3F%J?5PvQ0udu?q-T)j3JKV~NL>KRb~w-lWc685uS6 z=S#aR&B8Sc8>cGJ!!--?kwsJTUUm`Jk?7`H z7PrO~xgBrSW2_tTlCq1LH8*!o?pj?qxy8}(=r_;G18POrFh#;buWR0qU24+XUaVZ0 z?(sXcr@-YqvkCmHr{U2oPogHL{r#3r49TeR<{SJX1pcUqyWPrkYz^X8#QW~?F)R5i z>p^!i<;qM8Nf{-fd6!_&V*e_9qP6q(s<--&1Ttj01j0w>bXY7y1W*%Auu&p|XSOH=)V7Bd4fUKh&T1)@cvqhuD-d=?w}O zjI%i(f|thk0Go*!d7D%0^ztBfE*V=(ZIN84f5HU}T9?ulmEYzT5usi=DeuI*d|;M~ zp_=Cx^!4k#=m_qSPBr5EK~E?3J{dWWPH&oCcNepYVqL?nh4D5ynfWip$m*YlZ8r^Z zuFEUL-nW!3qjRCLIWPT0x)FDL7>Yt7@8dA?R2kF@WE>ysMY+)lTsgNM#3VbXVGL}F z1O(>q>2a+_`6r5Xv$NZAnp=Kgnr3)cL(^=8ypEeOf3q8(HGe@7Tt59;yFl||w|mnO zHDxg2G3z8=(6wjj9kbcEY@Z0iOd7Gq5GiPS5% z*sF1J<#daxDV2Z8H>wxOF<;yKzMeTaSOp_|XkS9Sfn6Mpe9UBi1cSTieGG5$O;ZLIIJ60Y>SN4vC?=yE_CWlo(EEE$e4j?z&^FM%kNmRtlbEL^dPPgvs9sbK5fGw*r@ z+!EU@u$T8!nZh?Fdf_qk$VuHk^yVw`h`_#KoS*N%epIIOfQUy_&V}VWDGp3tplMbf z5Se1sJUC$7N0F1-9jdV2mmGK{-}fu|Nv;12jDy0<-kf^AmkDnu6j~TPWOgy1MT68|D z=4=50jVbUKdKaQgD`eWGr3I&^<6uhkjz$YwItY8%Yp9{z4-{6g{73<_b*@XJ4Nm3-3z z?BW3{aY_ccRjb@W1)i5nLg|7BnWS!B`_Uo9CWaE`Ij327QH?i)9A}4Ug4wmxVVa^b z-4+m%-wwOl7cKH7+=x&nrCrbEC)Q$fpg&V83#uEH;C=GNMz`ps@^RxK%T*8%OPnC` z{WO~J%nxYJ`x|N%?&i7?;{_8t^jM&=50HlaOQj8fS}_`moH$c;vI<|cruPFnpT8yU zS%rPOCUSd5Zdb(zwk`hqwTQn)*&n)uYsP*F_(~xEWq}C= zv30kFmZFwJZ@ELVX3?$dXQh|icO7UrL*_5G=I^xXjImz`ZPp>?g#tf(ej~KaIU0algsG!IS09;>?MvqGg#c{i+}qY|{P8W~O%#>|gFd z<1dr$-oxyRGN17yZo1OwLnzwYs0|;IS_nymNB0IlSzPQ%-r`?T=;_XQ^~&#}b|AB} zkNbN5uB?-sUB-T5QLlg%Uk3)uHB;>VIzGe9_J9 zaeISkQm!v(9d(0ML^b9fR^sfHFlH?7Mvddt37OuR{|O0{uv)(&-6<87W4 zyO>s!=cPgP3O&7xxU5DlIPw_o3O>6o6Qb?JWs3qw#p3sBc3g$?Dx zi(6D+DYgV;GrUis-CL%Qe{nvZnwaVXmbhH(|GFh|Q)k=1uvA$I@1DXI7bKlQ@8D6P zS?(*?><>)G49q0wr;NajpxP4W2G)kHl6^=Z>hrNEI4Mwd_$O6$1dXF;Q#hE(-eeW6 zz03GJF%Wl?HO=_ztv5*zRlcU~{+{k%#N59mgm~eK>P!QZ6E?#Cu^2)+K8m@ySvZ*5 z|HDT}BkF@3!l(0%75G=1u2hETXEj!^1Z$!)!lyGXlWD!_vqGE$Z)#cUVBqlORW>0^ zDjyVTxwKHKG|0}j-`;!R-p>}qQfBl(?($7pP<+Y8QE#M8SCDq~k<+>Q^Zf@cT_WdX3~BSe z+|KK|7OL5Hm5(NFP~j>Ct3*$wi0n0!xl=(C61`q&cec@mFlH(sy%+RH<=s)8aAPN`SfJdkAQjdv82G5iRdv8 zh{9wHUZaniSEpslXl^_ODh}mypC?b*9FzLjb~H@3DFSe;D(A-K3t3eOTB(m~I6C;(-lKAvit(70k`%@+O*Ztdz;}|_TS~B?Tpmi=QKC^m_ z2YpEaT3iiz*;T~ap1yiA)a`dKMwu`^UhIUeltNQ1Yjo=q@bI@&3zH?rVUg=IxLy-ni zyxDu%-Fr{H6owTjZU2O5>nDb=q&Jz_TjeSq%!2m40x&U6w~GQ({quPL73IsJS;f`$ zsuhioqCBj(gJ>2hoo)Gou7(WP*pX)f=Y=!=k!&1K?EYY%jJ~X&DnK{^saPQK<1BJ z_A`_{%ZozcB(3w$z^To^6d|XuT@=X~wtW!+{4ID@N{AB~J6AL5vuY>JwvWCNFKsKh zd}@>q@_WV#QZ&UJ0#?X(pXR!oyXOEG3rqzHbCzGLONDb042i$})fM@XF)uSP(DHUc z^&{|$*xe{cs?Gp8=B%RY3L7#$ve$?TWh>MZdxF1zH1v}1z+$Ov#G7?%D)bBCyDe*% zSeKSpETC2V1){II>@UwJi>4uBN+iAx+82E~gb|Cr&8E^i&)A!uv-g?jzH99wU}8+# z$nh>yvb;TwZmS@7LrvuCu_d0-WxFNI&C7%sWuTL%YU!l|I1{|->=dlOeHOCtUO#zkS3ESO8LHV4hTdQL5EdV zuWD33fFPH}HPrW^s$Qn1Xgp&AT6<-He{{4%eIu3rN=iK|9mURdKXfB&Q?qGok%!cs ze53UP{Z!TO-Y@q2;;k2avA3`lm4OoN4@S*k=UA)7H;qZ`d8`XaYFCv?Ba+uGW@r5v z&&{nf(24WSBOhc7!qF^@0cz;XcUynNaj6w2349;s!K{KVqs5yS{ z7VubS`2OzT^5#1~6Tt^RTvt9-J|D2F>y~>2;jeF>g`hx5l%B3H=aLExQihuYngzlnBTYOTHJQMzl>kwqN5JYs)Ej zblA@ntkUS~xi+}y6|(81helS}Q~&VB37qyV|S3Y=><^1wh%msQM?fz z<58MX(=|PSUKCF#)dbhR%D&xgCD?$aR0qen+wpp6 zst}vX18!Be96TD??j1HsHTUx(a&@F?=gT`Q$oJFFyrh^;zgz!(NlAHGn0cJy@us=w zNhC#l5G;H}+>49Nsh12=ZPO2r*2OBQe5kpb&1?*PIBFitK8}FUfb~S-#hKfF0o#&d z#3aPkB$9scYku&kA6{0xHnBV#&Wei5J>5T-XX-gUXEPo+9b7WL=*XESc(3BshL`aj zXp}QIp*40}oWJt*l043e8_5;H5PI5c)U&IEw5dF(4zjX0y_lk9 zAp@!mK>WUqHo)-jop=DoK>&no>kAD=^qIE7qis&_*4~ z6q^EF$D@R~3_xseCG>Ikb6Gfofb$g|75PPyyZN&tiRxqovo_k zO|HA|sgy#B<32gyU9x^&)H$1jvw@qp+1b(eGAb)O%O!&pyX@^nQd^9BQ4{(F8<}|A zhF&)xusQhtoXOOhic=8#Xtt5&slLia3c*a?dIeczyTbC#>FTfiLST57nc3@Y#v_Eg#VUv zT8cKH#f3=1PNj!Oroz_MAR*pow%Y0*6YCYmUy^7`^r|j23Q~^*TW#cU7CHf0eAD_0 zEWEVddxFgQ7=!nEBQ|ibaScslvhuUk^*%b#QUNrEB{3PG@uTxNwW}Bs4$nS9wc(~O zG7Iq>aMsYkcr!9#A;HNsJrwTDYkK8ikdj{M;N$sN6BqJ<8~z>T20{J8Z2rRUuH7~3 z=tgS`AgxbBOMg87UT4Lwge`*Y=01Dvk>)^{Iu+n6fuVX4%}>?3czOGR$0 zpp*wp>bsFFSV`V;r_m+TZns$ZprIi`OUMhe^cLE$2O+pP3nP!YB$ry}2THx2QJs3< za1;>d-AggCarrQ>&Z!d@;mW+!q6eXhb&`GbzUDSxpl8AJ#Cm#tuc)_xh(2NV=5XMs zrf_ozRYO$NkC=pKFX5OH8v1>0i9Z$ec`~Mf+_jQ68spn(CJwclDhEEkH2Qw;${J$clv__nUjn5jA0wCLEnu1j;v!0vB>Ri6m9`;R{JMS%^)4FC zU0Z44+u$I$w=Bj|iu4DT5h~sS`C*zbmX?@-crY}E+hy>}2~C0Nn(EKk@5^qO4@l@! z6O0lr%tzGC`D^)8xU3FnMZVm0kX1sBWhaQyzVoXFWwr%Ny?=2M{5s#5i7fTu3gEkG zc{(Pr$v=;`Y#&`y*J}#M9ux>0?xu!`$9cUKm#Bdd_&S#LPTS?ZPV6zN6>W6JTS~-LfjL{mB=b(KMk3 z2HjBSlJeyUVqDd=Mt!=hpYsvby2GL&3~zm;0{^nZJq+4vb?5HH4wufvr}IX42sHeK zm@x?HN$8TsTavXs)tLDFJtY9b)y~Tl@7z4^I8oUQq4JckH@~CVQ;FoK(+e0XAM>1O z(ei}h?)JQp>)d=6ng-BZF1Z5hsAKW@mXq+hU?r8I(*%`tnIIOXw7V6ZK(T9RFJJe@ zZS!aC+p)Gf2Ujc=a6hx4!A1Th%YH!Lb^xpI!Eu` zmJO{9rw){B1Ql18d%F%da+Tbu1()?o(zT7StYqK6_w`e+fjXq5L^y(0 z09QA6H4oFj59c2wR~{~>jUoDzDdKz}5#onYPJRwa`SUO)Pd4)?(ENBaFVLJr6Kvz= zhTtXqbx09C1z~~iZt;g^9_2nCZ{};-b4dQJbv8HsWHXPVg^@(*!@xycp#R?a|L!+` zY5w))JWV`Gls(=}shH0#r*;~>_+-P5Qc978+QUd>J%`fyn{*TsiG-dWMiJXNgwBaT zJ=wgYFt+1ACW)XwtNx)Q9tA2LPoB&DkL16P)ERWQlY4%Y`-5aM9mZ{eKPUgI!~J3Z zkMd5A_p&v?V-o-6TUa8BndiX?ooviev(DKw=*bBVOW|=zps9=Yl|-R5@yJe*BPzN}a0mUsLn{4LfjB_oxpv(mwq# zSY*%E{iB)sNvWfzg-B!R!|+x(Q|b@>{-~cFvdDHA{F2sFGA5QGiIWy#3?P2JIpPKg6ncI^)dvqe`_|N=8 { diff --git a/domain-menu/src/main/java/com/nowait/menu/repository/MenuRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java similarity index 81% rename from domain-menu/src/main/java/com/nowait/menu/repository/MenuRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java index fbe2282b..80f07c95 100644 --- a/domain-menu/src/main/java/com/nowait/menu/repository/MenuRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java @@ -1,4 +1,4 @@ -package com.nowait.menu.repository; +package com.nowait.domaincorerdb.menu.repository; import java.util.List; import java.util.Optional; @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.entity.Menu; @Repository public interface MenuRepository extends JpaRepository { diff --git a/domain-order/src/main/java/com/nowait/order/entity/OrderItem.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderItem.java similarity index 84% rename from domain-order/src/main/java/com/nowait/order/entity/OrderItem.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderItem.java index a979933a..e23f943c 100644 --- a/domain-order/src/main/java/com/nowait/order/entity/OrderItem.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderItem.java @@ -1,7 +1,7 @@ -package com.nowait.order.entity; +package com.nowait.domaincorerdb.order.entity; -import com.nowait.menu.entity.Menu; -import com.nowait.base.BaseTimeEntity; +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; +import com.nowait.domaincorerdb.menu.entity.Menu; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; diff --git a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java similarity index 89% rename from domain-order/src/main/java/com/nowait/order/entity/UserOrder.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java index 06909f2c..3f2ae656 100644 --- a/domain-order/src/main/java/com/nowait/order/entity/UserOrder.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java @@ -1,10 +1,10 @@ -package com.nowait.order.entity; +package com.nowait.domaincorerdb.order.entity; import java.util.ArrayList; import java.util.List; -import com.nowait.store.entity.Store; -import com.nowait.base.BaseTimeEntity; +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; +import com.nowait.domaincorerdb.store.entity.Store; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; diff --git a/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/DepositorNameTooLongException.java similarity index 82% rename from domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/DepositorNameTooLongException.java index b5da206e..2767804f 100644 --- a/domain-order/src/main/java/com/nowait/order/exception/DepositorNameTooLongException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/DepositorNameTooLongException.java @@ -1,4 +1,4 @@ -package com.nowait.order.exception; +package com.nowait.domaincorerdb.order.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-order/src/main/java/com/nowait/order/exception/DuplicateOrderException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/DuplicateOrderException.java similarity index 80% rename from domain-order/src/main/java/com/nowait/order/exception/DuplicateOrderException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/DuplicateOrderException.java index aee663cd..c9311def 100644 --- a/domain-order/src/main/java/com/nowait/order/exception/DuplicateOrderException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/DuplicateOrderException.java @@ -1,4 +1,4 @@ -package com.nowait.order.exception; +package com.nowait.domaincorerdb.order.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-order/src/main/java/com/nowait/order/exception/OrderItemsEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderItemsEmptyException.java similarity index 80% rename from domain-order/src/main/java/com/nowait/order/exception/OrderItemsEmptyException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderItemsEmptyException.java index 416e101f..e3f6f730 100644 --- a/domain-order/src/main/java/com/nowait/order/exception/OrderItemsEmptyException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderItemsEmptyException.java @@ -1,4 +1,4 @@ -package com.nowait.order.exception; +package com.nowait.domaincorerdb.order.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-order/src/main/java/com/nowait/order/exception/OrderParameterEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderParameterEmptyException.java similarity index 81% rename from domain-order/src/main/java/com/nowait/order/exception/OrderParameterEmptyException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderParameterEmptyException.java index 197814c4..b50ccece 100644 --- a/domain-order/src/main/java/com/nowait/order/exception/OrderParameterEmptyException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderParameterEmptyException.java @@ -1,4 +1,4 @@ -package com.nowait.order.exception; +package com.nowait.domaincorerdb.order.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-order/src/main/java/com/nowait/order/repository/OrderItemRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderItemRepository.java similarity index 65% rename from domain-order/src/main/java/com/nowait/order/repository/OrderItemRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderItemRepository.java index 7544e3d0..b7cbc6cf 100644 --- a/domain-order/src/main/java/com/nowait/order/repository/OrderItemRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderItemRepository.java @@ -1,9 +1,9 @@ -package com.nowait.order.repository; +package com.nowait.domaincorerdb.order.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.order.entity.OrderItem; +import com.nowait.domaincorerdb.order.entity.OrderItem; @Repository public interface OrderItemRepository extends JpaRepository { diff --git a/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java similarity index 80% rename from domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index e41b80de..b07c96dd 100644 --- a/domain-order/src/main/java/com/nowait/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -1,4 +1,4 @@ -package com.nowait.order.repository; +package com.nowait.domaincorerdb.order.repository; import java.time.LocalDateTime; import java.util.List; @@ -6,8 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.order.entity.UserOrder; - +import com.nowait.domaincorerdb.order.entity.UserOrder; @Repository public interface OrderRepository extends JpaRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); diff --git a/domain-reservation/src/main/java/com/nowait/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java similarity index 90% rename from domain-reservation/src/main/java/com/nowait/reservation/entity/Reservation.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index 67463687..219f002d 100644 --- a/domain-reservation/src/main/java/com/nowait/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -1,10 +1,10 @@ -package com.nowait.reservation.entity; +package com.nowait.domaincorerdb.reservation.entity; import java.time.LocalDateTime; -import com.nowait.store.entity.Store; import com.nowait.common.enums.ReservationStatus; -import com.nowait.user.entity.User; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.user.entity.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNotFoundException.java similarity index 80% rename from domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNotFoundException.java index 3f65976f..4ce91721 100644 --- a/domain-reservation/src/main/java/com/nowait/reservation/exception/ReservationNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNotFoundException.java @@ -1,4 +1,4 @@ -package com.nowait.reservation.exception; +package com.nowait.domaincorerdb.reservation.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-reservation/src/main/java/com/nowait/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java similarity index 57% rename from domain-reservation/src/main/java/com/nowait/reservation/repository/ReservationRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index e564ff78..874469c8 100644 --- a/domain-reservation/src/main/java/com/nowait/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -1,11 +1,13 @@ -package com.nowait.reservation.repository; +package com.nowait.domaincorerdb.reservation.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; -import com.nowait.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.entity.Reservation; +@Repository public interface ReservationRepository extends JpaRepository { List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); } diff --git a/domain-store/src/main/java/com/nowait/store/entity/Store.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java similarity index 94% rename from domain-store/src/main/java/com/nowait/store/entity/Store.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java index c1458feb..08175e48 100644 --- a/domain-store/src/main/java/com/nowait/store/entity/Store.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java @@ -1,8 +1,8 @@ -package com.nowait.store.entity; +package com.nowait.domaincorerdb.store.entity; import java.time.LocalDateTime; -import com.nowait.base.BaseTimeEntity; +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -12,7 +12,6 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; diff --git a/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java similarity index 89% rename from domain-store/src/main/java/com/nowait/store/entity/StoreImage.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java index d657bd7b..e8c5d270 100644 --- a/domain-store/src/main/java/com/nowait/store/entity/StoreImage.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java @@ -1,6 +1,6 @@ -package com.nowait.store.entity; +package com.nowait.domaincorerdb.store.entity; -import com.nowait.base.BaseTimeEntity; +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreImageEmptyException.java similarity index 80% rename from domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreImageEmptyException.java index 0f65bb1e..174cb8d0 100644 --- a/domain-store/src/main/java/com/nowait/store/exception/StoreImageEmptyException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreImageEmptyException.java @@ -1,4 +1,4 @@ -package com.nowait.store.exception; +package com.nowait.domaincorerdb.store.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreImageNotFoundException.java similarity index 81% rename from domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreImageNotFoundException.java index aff9e0c0..f1b49fd1 100644 --- a/domain-store/src/main/java/com/nowait/store/exception/StoreImageNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreImageNotFoundException.java @@ -1,4 +1,4 @@ -package com.nowait.store.exception; +package com.nowait.domaincorerdb.store.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java similarity index 81% rename from domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java index 2b1fecc7..e69ad30b 100644 --- a/domain-store/src/main/java/com/nowait/store/exception/StoreKeywordEmptyException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java @@ -1,4 +1,4 @@ -package com.nowait.store.exception; +package com.nowait.domaincorerdb.store.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreNotFoundException.java similarity index 80% rename from domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreNotFoundException.java index 15d485db..ded420e0 100644 --- a/domain-store/src/main/java/com/nowait/store/exception/StoreNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreNotFoundException.java @@ -1,4 +1,4 @@ -package com.nowait.store.exception; +package com.nowait.domaincorerdb.store.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreParamEmptyException.java similarity index 81% rename from domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreParamEmptyException.java index 5dc0bf13..c33bc1bb 100644 --- a/domain-store/src/main/java/com/nowait/store/exception/StoreParamEmptyException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreParamEmptyException.java @@ -1,4 +1,4 @@ -package com.nowait.store.exception; +package com.nowait.domaincorerdb.store.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-store/src/main/java/com/nowait/store/repository/StoreImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java similarity index 63% rename from domain-store/src/main/java/com/nowait/store/repository/StoreImageRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java index 0617122e..80448ab8 100644 --- a/domain-store/src/main/java/com/nowait/store/repository/StoreImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java @@ -1,12 +1,12 @@ -package com.nowait.store.repository; +package com.nowait.domaincorerdb.store.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.store.entity.Store; -import com.nowait.store.entity.StoreImage; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.entity.StoreImage; @Repository public interface StoreImageRepository extends JpaRepository { diff --git a/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java similarity index 85% rename from domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index a0d04ffb..efeec3ca 100644 --- a/domain-store/src/main/java/com/nowait/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -1,4 +1,5 @@ -package com.nowait.store.repository; +package com.nowait.domaincorerdb.store.repository; + import java.util.List; import java.util.Optional; @@ -8,7 +9,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.store.entity.Store; +import com.nowait.domaincorerdb.store.entity.Store; @Repository public interface StoreRepository extends JpaRepository { diff --git a/domain-token/src/main/java/com/nowait/token/entity/Token.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java similarity index 89% rename from domain-token/src/main/java/com/nowait/token/entity/Token.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java index 16311bfa..14378dd6 100644 --- a/domain-token/src/main/java/com/nowait/token/entity/Token.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java @@ -1,8 +1,8 @@ -package com.nowait.token.entity; +package com.nowait.domaincorerdb.token.entity; import java.time.LocalDateTime; -import com.nowait.user.entity.User; +import com.nowait.domaincorerdb.user.entity.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -23,7 +23,8 @@ @Table(name = "token") @Getter public class Token { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long tokenId; @ManyToOne(fetch = FetchType.LAZY) diff --git a/domain-token/src/main/java/com/nowait/token/exception/BusinessException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/exception/BusinessException.java similarity index 87% rename from domain-token/src/main/java/com/nowait/token/exception/BusinessException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/exception/BusinessException.java index b9df2243..86886330 100644 --- a/domain-token/src/main/java/com/nowait/token/exception/BusinessException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.nowait.token.exception; +package com.nowait.domaincorerdb.token.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-token/src/main/java/com/nowait/token/repository/TokenRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java similarity index 56% rename from domain-token/src/main/java/com/nowait/token/repository/TokenRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java index 53d49d76..ab74bc46 100644 --- a/domain-token/src/main/java/com/nowait/token/repository/TokenRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java @@ -1,11 +1,13 @@ -package com.nowait.token.repository; +package com.nowait.domaincorerdb.token.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; -import com.nowait.token.entity.Token; +import com.nowait.domaincorerdb.token.entity.Token; +@Repository public interface TokenRepository extends JpaRepository { Optional findByUserId(Long userId); } diff --git a/domain-user/src/main/java/com/nowait/user/entity/MemberDetails.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java similarity index 96% rename from domain-user/src/main/java/com/nowait/user/entity/MemberDetails.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java index 247b5ae8..f9bc7b3f 100644 --- a/domain-user/src/main/java/com/nowait/user/entity/MemberDetails.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java @@ -1,4 +1,4 @@ -package com.nowait.user.entity; +package com.nowait.domaincorerdb.user.entity; import java.util.Collection; import java.util.List; diff --git a/domain-user/src/main/java/com/nowait/user/entity/User.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java similarity index 97% rename from domain-user/src/main/java/com/nowait/user/entity/User.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java index 71e68ecb..0bb965f4 100644 --- a/domain-user/src/main/java/com/nowait/user/entity/User.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java @@ -1,4 +1,4 @@ -package com.nowait.user.entity; +package com.nowait.domaincorerdb.user.entity; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java similarity index 80% rename from domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java index 74788aa9..aef89ab3 100644 --- a/domain-user/src/main/java/com/nowait/user/exception/UserNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java @@ -1,4 +1,4 @@ -package com.nowait.user.exception; +package com.nowait.domaincorerdb.user.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-user/src/main/java/com/nowait/user/repository/UserRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java similarity index 61% rename from domain-user/src/main/java/com/nowait/user/repository/UserRepository.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java index 331264a3..b22b9fc9 100644 --- a/domain-user/src/main/java/com/nowait/user/repository/UserRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java @@ -1,11 +1,13 @@ -package com.nowait.user.repository; +package com.nowait.domaincorerdb.user.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; -import com.nowait.user.entity.User; +import com.nowait.domaincorerdb.user.entity.User; +@Repository public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByNickname(String nickName); diff --git a/domain-menu/.gitignore b/nowait-domain/domain-user-rdb/.gitignore similarity index 100% rename from domain-menu/.gitignore rename to nowait-domain/domain-user-rdb/.gitignore diff --git a/domain-order/build.gradle b/nowait-domain/domain-user-rdb/build.gradle similarity index 58% rename from domain-order/build.gradle rename to nowait-domain/domain-user-rdb/build.gradle index 263db1df..09aa35f3 100644 --- a/domain-order/build.gradle +++ b/nowait-domain/domain-user-rdb/build.gradle @@ -2,6 +2,14 @@ plugins { id 'java-library' } +jar { + enabled = true +} +bootJar { + enabled = false +} + + group = 'com.nowaiting' version = rootProject.version @@ -16,14 +24,17 @@ repositories { } dependencies { - implementation project(':common') - implementation project(':domain-base') - implementation project(':domain-store') - implementation project(':domain-menu') + implementation project(':nowait-common') + implementation project(':nowait-domain:domain-core-rdb') api 'org.springframework.boot:spring-boot-starter-data-jpa' api 'jakarta.persistence:jakarta.persistence-api:3.1.0' + // OAuth + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' + compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' diff --git a/domain-bookmark/src/main/java/com/nowait/bookmark/entity/Bookmark.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java similarity index 84% rename from domain-bookmark/src/main/java/com/nowait/bookmark/entity/Bookmark.java rename to nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java index d1808277..1f5b3fa2 100644 --- a/domain-bookmark/src/main/java/com/nowait/bookmark/entity/Bookmark.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java @@ -1,8 +1,8 @@ -package com.nowait.bookmark.entity; +package com.nowait.domainuserrdb.bookmark.entity; -import com.nowait.store.entity.Store; -import com.nowait.base.BaseTimeEntity; -import com.nowait.user.entity.User; +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.user.entity.User; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; diff --git a/domain-bookmark/src/main/java/com/nowait/bookmark/exception/BookmarkOwnerMismatchException.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkOwnerMismatchException.java similarity index 80% rename from domain-bookmark/src/main/java/com/nowait/bookmark/exception/BookmarkOwnerMismatchException.java rename to nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkOwnerMismatchException.java index dcb2549a..0f7cdd9f 100644 --- a/domain-bookmark/src/main/java/com/nowait/bookmark/exception/BookmarkOwnerMismatchException.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkOwnerMismatchException.java @@ -1,4 +1,4 @@ -package com.nowait.bookmark.exception; +package com.nowait.domainuserrdb.bookmark.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-bookmark/src/main/java/com/nowait/bookmark/exception/DuplicateBookmarkException.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/DuplicateBookmarkException.java similarity index 80% rename from domain-bookmark/src/main/java/com/nowait/bookmark/exception/DuplicateBookmarkException.java rename to nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/DuplicateBookmarkException.java index d18b4371..7b9ef268 100644 --- a/domain-bookmark/src/main/java/com/nowait/bookmark/exception/DuplicateBookmarkException.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/DuplicateBookmarkException.java @@ -1,4 +1,4 @@ -package com.nowait.bookmark.exception; +package com.nowait.domainuserrdb.bookmark.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/domain-bookmark/src/main/java/com/nowait/bookmark/repository/BookmarkRepository.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java similarity index 61% rename from domain-bookmark/src/main/java/com/nowait/bookmark/repository/BookmarkRepository.java rename to nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java index e5a2a10d..69526299 100644 --- a/domain-bookmark/src/main/java/com/nowait/bookmark/repository/BookmarkRepository.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java @@ -1,13 +1,13 @@ -package com.nowait.bookmark.repository; +package com.nowait.domainuserrdb.bookmark.repository; import java.util.Collection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.store.entity.Store; -import com.nowait.bookmark.entity.Bookmark; -import com.nowait.user.entity.User; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domainuserrdb.bookmark.entity.Bookmark; @Repository public interface BookmarkRepository extends JpaRepository { diff --git a/external-oauth/src/main/java/com/nowait/externaloauth/dto/CustomOAuth2User.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java similarity index 92% rename from external-oauth/src/main/java/com/nowait/externaloauth/dto/CustomOAuth2User.java rename to nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java index 41a5b1b1..f163bca7 100644 --- a/external-oauth/src/main/java/com/nowait/externaloauth/dto/CustomOAuth2User.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java @@ -1,4 +1,4 @@ -package com.nowait.externaloauth.dto; +package com.nowait.domainuserrdb.oauth.dto; import java.util.ArrayList; import java.util.Collection; @@ -7,7 +7,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.user.OAuth2User; -import com.nowait.user.entity.User; +import com.nowait.domaincorerdb.user.entity.User; import lombok.RequiredArgsConstructor; diff --git a/common/.gitattributes b/nowait-infra/.gitattributes similarity index 100% rename from common/.gitattributes rename to nowait-infra/.gitattributes diff --git a/domain-order/.gitignore b/nowait-infra/.gitignore similarity index 100% rename from domain-order/.gitignore rename to nowait-infra/.gitignore diff --git a/infra-aws/build.gradle b/nowait-infra/build.gradle similarity index 93% rename from infra-aws/build.gradle rename to nowait-infra/build.gradle index c4a109cf..aa478c84 100644 --- a/infra-aws/build.gradle +++ b/nowait-infra/build.gradle @@ -2,6 +2,14 @@ plugins { id 'java' } +jar { + enabled = true +} +bootJar { + enabled = false +} + + group = 'com.nowaiting' version = rootProject.version @@ -19,13 +27,13 @@ dependencies { // Multipart File Upload // 추후 분리 필요 implementation 'org.springframework.boot:spring-boot-starter-web' + // 비동기 실행 + implementation 'org.springframework.boot:spring-boot-starter-aop' // S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' // Resilience4j implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' implementation 'io.github.resilience4j:resilience4j-bulkhead:2.3.0' - // 비동기 실행 - implementation 'org.springframework.boot:spring-boot-starter-aop' // lombok compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' diff --git a/infra-aws/src/main/java/com/nowait/infraaws/s3/S3Service.java b/nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java similarity index 98% rename from infra-aws/src/main/java/com/nowait/infraaws/s3/S3Service.java rename to nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java index a2c586fc..18416c32 100644 --- a/infra-aws/src/main/java/com/nowait/infraaws/s3/S3Service.java +++ b/nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java @@ -1,4 +1,4 @@ -package com.nowait.infraaws.s3; +package com.nowait.infraaws.aws.s3; import java.io.InputStream; import java.util.UUID; diff --git a/application-config/src/main/java/com/nowait/config/config/AsyncConfig.java b/nowait-infra/src/main/java/com/nowait/infraaws/config/AsyncConfig.java similarity index 94% rename from application-config/src/main/java/com/nowait/config/config/AsyncConfig.java rename to nowait-infra/src/main/java/com/nowait/infraaws/config/AsyncConfig.java index 0d63871f..78ea0dbe 100644 --- a/application-config/src/main/java/com/nowait/config/config/AsyncConfig.java +++ b/nowait-infra/src/main/java/com/nowait/infraaws/config/AsyncConfig.java @@ -1,4 +1,4 @@ -package com.nowait.config.config; +package com.nowait.infraaws.config; import java.util.concurrent.Executor; diff --git a/infra-aws/src/main/java/com/nowait/infraaws/config/AwsS3Config.java b/nowait-infra/src/main/java/com/nowait/infraaws/config/AwsS3Config.java similarity index 100% rename from infra-aws/src/main/java/com/nowait/infraaws/config/AwsS3Config.java rename to nowait-infra/src/main/java/com/nowait/infraaws/config/AwsS3Config.java diff --git a/security-admin/.gitattributes b/security-admin/.gitattributes deleted file mode 100644 index 8af972cd..00000000 --- a/security-admin/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -/gradlew text eol=lf -*.bat text eol=crlf -*.jar binary diff --git a/security-admin/.gitignore b/security-admin/.gitignore deleted file mode 100644 index c2065bc2..00000000 --- a/security-admin/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -HELP.md -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ diff --git a/security-admin/build.gradle b/security-admin/build.gradle deleted file mode 100644 index 9c9de646..00000000 --- a/security-admin/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - id 'java-library' -} - -group = 'com.nowaiting' -version = rootProject.version - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':common') - implementation project(':domain-user') - implementation project(':domain-base') - implementation project(':domain-token') - implementation project(':application-config') - // SPRING SECURITY - api 'org.springframework.boot:spring-boot-starter-security' - api 'org.springframework.boot:spring-boot-starter-test' - // Lombok (optional) - compileOnly 'org.projectlombok:lombok:1.18.26' - annotationProcessor 'org.projectlombok:lombok:1.18.26' - // jwt - implementation 'io.jsonwebtoken:jjwt-api:0.12.3' - implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' - implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' - implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' - implementation 'com.auth0:java-jwt:4.4.0' - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' -} diff --git a/security-front/.gitignore b/security-front/.gitignore deleted file mode 100644 index c2065bc2..00000000 --- a/security-front/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -HELP.md -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ diff --git a/security-front/build.gradle b/security-front/build.gradle deleted file mode 100644 index 6201e438..00000000 --- a/security-front/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id 'java-library' -} - -group = 'com.nowaiting' -version = rootProject.version - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':common') - implementation project(':domain-user') - implementation project(':domain-base') - implementation project(':domain-token') - implementation project(':external-oauth') - implementation project(':application-config') - // SPRING SECURITY - api 'org.springframework.boot:spring-boot-starter-security' - api 'org.springframework.boot:spring-boot-starter-test' - // Lombok (optional) - compileOnly 'org.projectlombok:lombok:1.18.26' - annotationProcessor 'org.projectlombok:lombok:1.18.26' - // jwt - implementation 'io.jsonwebtoken:jjwt-api:0.12.3' - implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' - implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' - // OAUTH2 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' - implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' -} diff --git a/settings.gradle b/settings.gradle index 4984deb9..24f20718 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,19 +1,9 @@ rootProject.name = 'nowait' -include 'common' -include 'domain-base' -include 'domain-user' -include 'domain-menu' -include 'domain-store' -include 'domain-token' -include 'domain-bookmark' -include 'domain-order' -include 'domain-reservation' -include 'security-admin' -include 'security-front' -include 'external-oauth' -include 'infra-aws' -include 'application-config' -include 'application-user' -include 'application-admin' - +include 'nowait-app-user-api' +include 'nowait-app-admin-api' +include 'nowait-common' +include 'nowait-domain:domain-core-rdb' +include 'nowait-domain:domain-admin-rdb' +include 'nowait-domain:domain-user-rdb' +include 'nowait-infra' From ca8b1405f7104e34101d60c48345fae5eafb6752 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:02:45 +0900 Subject: [PATCH 088/685] =?UTF-8?q?refactor:=20=EB=A9=80=ED=8B=B0=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 21 ++++++++++----------- .github/workflows/deploy-user.yml | 21 ++++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 5d28a479..39de4768 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -5,12 +5,11 @@ on: branches: - develop paths: - - 'application-admin/**' - - 'application-config/**' - - 'common/**' - - 'domain-*/**' - - 'security-admin/**' - - 'infra-aws/**' + - 'nowait-app-admin-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-admin-rdb/**' + - 'nowait-infra/**' - 'build.gradle' - 'settings.gradle' - 'gradle/**' @@ -41,14 +40,14 @@ jobs: - name: Set YML run: | - mkdir -p application-admin/src/main/resources + mkdir -p nowait-app-admin-api/src/main/resources echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" \ | base64 --decode \ - > application-admin/src/main/resources/application-admin.yml - find application-admin/src + > nowait-app-admin-api/src/main/resources/application-admin.yml + find nowait-app-admin-api/src - name: Build Admin JAR - run: ./gradlew clean :application-admin:bootJar -x test + run: ./gradlew clean :nowait-app-admin-api:bootJar -x test - name: Prepare deployment package run: | @@ -56,7 +55,7 @@ jobs: mkdir -p deploy/scripts cp scripts/start-admin.sh scripts/stop-admin.sh deploy/scripts/ cp appspec-admin.yml deploy/appspec.yml - cp application-admin/build/libs/application-admin-*.jar deploy/application-admin.jar + cp nowait-app-admin-api/build/libs/nowait-app-admin-api-*.jar deploy/nowait-app-admin-api.jar cd deploy zip -r ../NoWait-admin-${GITHUB_SHA}.zip . cd .. diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index f5162197..70f31909 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -5,12 +5,11 @@ on: branches: - develop paths: - - 'application-user/**' - - 'application-config/**' - - 'common/**' - - 'domain-*/**' - - 'security-front/**' - - 'external-oauth/**' + - 'nowait-app-user-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-user-rdb/**' + - 'nowait-infra/**' - 'build.gradle' - 'settings.gradle' - 'gradle/**' @@ -41,14 +40,14 @@ jobs: - name: Set YML run: | - mkdir -p application-user/src/main/resources + mkdir -p nowait-app-user-api/src/main/resources echo "${{ secrets.APPLICATION_DEV_USER_YML }}" \ | base64 --decode \ - > application-user/src/main/resources/application-user.yml - find application-user/src + > nowait-app-user-api/src/main/resources/nowait-app-user-api.yml + find nowait-app-user-api/src - name: Build User JAR - run: ./gradlew clean :application-user:bootJar -x test + run: ./gradlew clean :nowait-app-user-api:bootJar -x test - name: Prepare deployment package run: | @@ -56,7 +55,7 @@ jobs: mkdir -p deploy/scripts cp scripts/start-user.sh scripts/stop-user.sh deploy/scripts/ cp appspec-user.yml deploy/appspec.yml - cp application-user/build/libs/application-user-*.jar deploy/application-user.jar + cp nowait-app-user-api/build/libs/nowait-app-user-api-*.jar deploy/nowait-app-user-api.jar cd deploy zip -r ../NoWait-user-${GITHUB_SHA}.zip . cd .. From 48cc4fcbcdffec91c1b5c8e253bba7f2896188f0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:37:44 +0900 Subject: [PATCH 089/685] =?UTF-8?q?refactor:=20=EB=A9=80=ED=8B=B0=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 16 ++++++++-------- .github/workflows/deploy-user.yml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 39de4768..5c9889c2 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -5,14 +5,14 @@ on: branches: - develop paths: - - 'nowait-app-admin-api/**' - - 'nowait-common/**' - - 'nowait-domain/domain-core-rdb/**' - - 'nowait-domain/domain-admin-rdb/**' - - 'nowait-infra/**' - - 'build.gradle' - - 'settings.gradle' - - 'gradle/**' + - 'nowait-app-admin-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-admin-rdb/**' + - 'nowait-infra/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' env: PROJECT_NAME: NoWait diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index 70f31909..67b2f28f 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -5,14 +5,14 @@ on: branches: - develop paths: - - 'nowait-app-user-api/**' - - 'nowait-common/**' - - 'nowait-domain/domain-core-rdb/**' - - 'nowait-domain/domain-user-rdb/**' - - 'nowait-infra/**' - - 'build.gradle' - - 'settings.gradle' - - 'gradle/**' + - 'nowait-app-user-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-user-rdb/**' + - 'nowait-infra/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' env: PROJECT_NAME: NoWait From 28ba7b8fb7f576b77f690f9bc0f5f585d8ea0eb6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:38:12 +0900 Subject: [PATCH 090/685] =?UTF-8?q?feat(order):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20Order=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 2 +- .../order/dto/OrderResponseDto.java | 31 ++++++++++++++++ .../dto/OrderStatusUpdateRequestDto.java | 16 +++++++++ .../dto/OrderStatusUpdateResponseDto.java | 17 +++++++++ .../order/service/OrderService.java | 36 +++++++++++++++++++ .../order/service/OrderService.java | 1 + .../order/entity/OrderStatus.java | 2 +- 7 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 775306e8..dfc03619 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -25,7 +25,7 @@ @Tag(name = "Order API", description = "주문 API") @RestController -@RequestMapping("admin/orders") +@RequestMapping("/admin/orders") @RequiredArgsConstructor public class OrderController { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java new file mode 100644 index 00000000..f5e93610 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -0,0 +1,31 @@ +package com.nowait.applicationadmin.order.dto; + +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.order.entity.OrderStatus; +import com.nowait.domaincorerdb.order.entity.UserOrder; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class OrderResponseDto { + private Long id; + private Long tableId; + private String depositorName; + private Integer totalPrice; + private OrderStatus status; + private LocalDateTime createdAt; + + public static OrderResponseDto fromEntity(UserOrder userOrder) { + return OrderResponseDto.builder() + .id(userOrder.getId()) + .tableId(userOrder.getTableId()) + .depositorName(userOrder.getDepositorName()) + .totalPrice(userOrder.getTotalPrice()) + .status(userOrder.getStatus()) + .createdAt(userOrder.getCreatedAt()) + .build(); + } +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java new file mode 100644 index 00000000..a28f0434 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java @@ -0,0 +1,16 @@ +package com.nowait.applicationadmin.order.dto; + +import com.nowait.domaincorerdb.order.entity.OrderStatus; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class OrderStatusUpdateRequestDto { + @Schema(description = "주문 상태", example = "WAITING_FOR_PAYMENT", allowableValues = {"WAITING_FOR_PAYMENT", "COOKING", "COOKED"}) + @NotNull(message = "주문상태는 필수입니다") + private final OrderStatus orderStatus; +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java new file mode 100644 index 00000000..dfcec919 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java @@ -0,0 +1,17 @@ +package com.nowait.applicationadmin.order.dto; + +import com.nowait.domaincorerdb.order.entity.OrderStatus; +import com.nowait.domaincorerdb.order.entity.UserOrder; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class OrderStatusUpdateResponseDto { + private final OrderStatus orderStatus; + + public static OrderStatusUpdateResponseDto fromEntity(UserOrder userOrder) { + return new OrderStatusUpdateResponseDto(userOrder.getStatus()); + } +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java new file mode 100644 index 00000000..41418531 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -0,0 +1,36 @@ +package com.nowait.applicationadmin.order.service; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationadmin.order.dto.OrderResponseDto; +import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; +import com.nowait.domaincorerdb.order.entity.OrderStatus; +import com.nowait.domaincorerdb.order.entity.UserOrder; +import com.nowait.domaincorerdb.order.repository.OrderRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OrderService { + private final OrderRepository orderRepository; + + @Transactional(readOnly = true) + public List findAllOrders(Long storeId) { + return orderRepository.findAllByStore_StoreId(storeId).stream() + .map(OrderResponseDto::fromEntity) + .collect(Collectors.toList()); + } + + @Transactional + public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus) { + UserOrder userOrder = orderRepository.findById(orderId) + .orElseThrow(() -> new IllegalArgumentException("Order not found with id: " + orderId)); + userOrder.updateStatus(newStatus); + return OrderStatusUpdateResponseDto.fromEntity(userOrder); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index a3f59ebf..1f0ed53c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -18,6 +18,7 @@ import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.order.entity.OrderItem; +import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java index 7a111111..98b50744 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java @@ -1,4 +1,4 @@ -package com.nowait.order.entity; +package com.nowait.domaincorerdb.order.entity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; From 012c9f699b4b669ff1afbecd53c7d02bcd35d42b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:38:33 +0900 Subject: [PATCH 091/685] =?UTF-8?q?refactor:=20=EB=A9=80=ED=8B=B0=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/dto/OrderResponseDto.java | 33 ----------- .../dto/OrderStatusUpdateRequestDto.java | 16 ------ .../dto/OrderStatusUpdateResponseDto.java | 17 ------ .../order/service/OrderService.java | 37 ------------ application-config/build.gradle | 0 .../applicationconfig/config/AsyncConfig.java | 21 ------- .../applicationconfig/config/CorsConfig.java | 28 --------- .../nowait/config/config/SwaggerConfig.java | 30 ---------- build.gradle | 4 +- .../com/example/domaintoken/entity/Token.java | 54 ------------------ .../exception/BusinessException.java | 16 ------ .../repository/TokenRepository.java | 11 ---- .../infrastorage/config/AwsS3Config.java | 33 ----------- .../example/infrastorage/s3/S3Service.java | 57 ------------------- nowait-app-admin-api/build.gradle | 9 ++- nowait-app-user-api/build.gradle | 7 +-- nowait-common/build.gradle | 3 - nowait-domain/build.gradle | 6 +- nowait-domain/domain-admin-rdb/build.gradle | 3 - nowait-domain/domain-core-rdb/build.gradle | 7 +-- nowait-domain/domain-user-rdb/build.gradle | 6 +- nowait-infra/build.gradle | 3 - 22 files changed, 18 insertions(+), 383 deletions(-) delete mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java delete mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java delete mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java delete mode 100644 application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java delete mode 100644 application-config/build.gradle delete mode 100644 application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java delete mode 100644 application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java delete mode 100644 application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java delete mode 100644 domain-token/src/main/java/com/example/domaintoken/entity/Token.java delete mode 100644 domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java delete mode 100644 domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java delete mode 100644 infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java delete mode 100644 infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java deleted file mode 100644 index afb2a079..00000000 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.nowait.applicationadmin.order.dto; - -import java.time.LocalDateTime; - -import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; -import com.nowait.order.entity.UserOrder; -import com.nowait.order.entity.OrderStatus; -import com.nowait.reservation.entity.Reservation; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class OrderResponseDto { - private Long id; - private Long tableId; - private String depositorName; - private Integer totalPrice; - private OrderStatus status; - private LocalDateTime createdAt; - - public static OrderResponseDto fromEntity(UserOrder userOrder) { - return OrderResponseDto.builder() - .id(userOrder.getId()) - .tableId(userOrder.getTableId()) - .depositorName(userOrder.getDepositorName()) - .totalPrice(userOrder.getTotalPrice()) - .status(userOrder.getStatus()) - .createdAt(userOrder.getCreatedAt()) - .build(); - } -} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java deleted file mode 100644 index a8a78442..00000000 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateRequestDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.nowait.applicationadmin.order.dto; - -import com.nowait.order.entity.OrderStatus; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class OrderStatusUpdateRequestDto { - @Schema(description = "주문 상태", example = "WAITING_FOR_PAYMENT", allowableValues = {"WAITING_FOR_PAYMENT", "COOKING", "COOKED"}) - @NotNull(message = "주문상태는 필수입니다") - private final OrderStatus orderStatus; -} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java deleted file mode 100644 index 63de5b53..00000000 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/dto/OrderStatusUpdateResponseDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.nowait.applicationadmin.order.dto; - -import com.nowait.order.entity.OrderStatus; -import com.nowait.order.entity.UserOrder; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class OrderStatusUpdateResponseDto { - private final OrderStatus orderStatus; - - public static OrderStatusUpdateResponseDto fromEntity(UserOrder userOrder) { - return new OrderStatusUpdateResponseDto(userOrder.getStatus()); - } -} diff --git a/application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java deleted file mode 100644 index c06986cf..00000000 --- a/application-admin/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.nowait.applicationadmin.order.service; - -import java.util.List; -import java.util.stream.Collectors; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.nowait.applicationadmin.order.dto.OrderResponseDto; -import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; -import com.nowait.order.entity.OrderStatus; -import com.nowait.order.entity.UserOrder; -import com.nowait.order.repository.OrderRepository; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class OrderService { - - private final OrderRepository orderRepository; - - @Transactional(readOnly = true) - public List findAllOrders(Long storeId) { - return orderRepository.findAllByStore_StoreId(storeId).stream() - .map(OrderResponseDto::fromEntity) - .collect(Collectors.toList()); - } - - @Transactional - public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus) { - UserOrder userOrder = orderRepository.findById(orderId) - .orElseThrow(() -> new IllegalArgumentException("Order not found with id: " + orderId)); - userOrder.updateStatus(newStatus); - return OrderStatusUpdateResponseDto.fromEntity(userOrder); - } -} diff --git a/application-config/build.gradle b/application-config/build.gradle deleted file mode 100644 index e69de29b..00000000 diff --git a/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java b/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java deleted file mode 100644 index 9c839fbb..00000000 --- a/application-config/src/main/java/com/example/applicationconfig/config/AsyncConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.applicationconfig.config; - -import java.util.concurrent.Executor; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -@Configuration -public class AsyncConfig { - @Bean(name = "s3UploadExecutor") - public Executor s3UploadExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(5); - executor.setMaxPoolSize(10); - executor.setQueueCapacity(100); - executor.setThreadNamePrefix("S3Upload-"); - executor.initialize(); - return executor; - } -} diff --git a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java b/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java deleted file mode 100644 index d8615194..00000000 --- a/application-config/src/main/java/com/example/applicationconfig/config/CorsConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.applicationconfig.config; - -import java.util.List; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -@Configuration -public class CorsConfig { - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - - config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:3000")); // 허용할 출처 설정 - config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 - config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 - config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - // config.setAllowCredentials(true); // 쿠키 포함 허용 - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 - return source; - } -} diff --git a/application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java b/application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java deleted file mode 100644 index 7c6454c0..00000000 --- a/application-config/src/main/java/com/nowait/config/config/SwaggerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.nowait.config.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; - -@Configuration -public class SwaggerConfig { - - @Bean - public OpenAPI openAPI() { - return new OpenAPI() - .addSecurityItem(new SecurityRequirement().addList("JWT")) - .components(new Components() - .addSecuritySchemes("JWT", new SecurityScheme() - .name("Authorization") - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - .in(SecurityScheme.In.HEADER))) - .info(new Info().title("NOWAIT API") - .description("NOWAIT API Specification") - .version("v0.0.1")); - } -} diff --git a/build.gradle b/build.gradle index 03ccdb50..94b2c6d6 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,9 @@ subprojects { apply plugin: "java" apply plugin: 'java-library' apply plugin: "io.spring.dependency-management" - apply plugin: "org.springframework.boot" + if (project.name.startsWith('nowait-app-')) { + apply plugin: "org.springframework.boot" + } repositories { mavenCentral() diff --git a/domain-token/src/main/java/com/example/domaintoken/entity/Token.java b/domain-token/src/main/java/com/example/domaintoken/entity/Token.java deleted file mode 100644 index 3bd47167..00000000 --- a/domain-token/src/main/java/com/example/domaintoken/entity/Token.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.example.domaintoken.entity; - -import java.time.LocalDateTime; - -import com.nowait.user.entity.User; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "token") -@Getter -public class Token { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long tokenId; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column - private String refreshToken; - - private LocalDateTime expiredDate; - - @Builder - public Token(User user, String refreshToken, LocalDateTime expiredDate) { - this.user = user; - this.refreshToken = refreshToken; - this.expiredDate = expiredDate; - } - - // static method로 객체를 생성 - 생성 의도 파악 쉬웁 - public static Token toEntity(User user, String refreshToken, LocalDateTime expiredDate){ - return Token.builder() - .user(user) - .refreshToken(refreshToken) - .expiredDate(expiredDate) - .build(); - } - -} diff --git a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java b/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java deleted file mode 100644 index 7fe9c748..00000000 --- a/domain-token/src/main/java/com/example/domaintoken/exception/BusinessException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.domaintoken.exception; - -import com.nowait.common.exception.ErrorMessage; - -public abstract class BusinessException extends RuntimeException { - private final ErrorMessage errorMessage; - - protected BusinessException(ErrorMessage errorMessage) { - super(errorMessage.getMessage()); - this.errorMessage = errorMessage; - } - - public String getCode() { - return errorMessage.getCode(); - } -} diff --git a/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java b/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java deleted file mode 100644 index d813fd09..00000000 --- a/domain-token/src/main/java/com/example/domaintoken/repository/TokenRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.domaintoken.repository; - -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -import com.example.domaintoken.entity.Token; - -public interface TokenRepository extends JpaRepository { - Optional findByUserId(Long userId); -} diff --git a/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java b/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java deleted file mode 100644 index 4eda87c2..00000000 --- a/infra-aws/src/main/java/com/example/infrastorage/config/AwsS3Config.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.infrastorage.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; - -@Configuration -public class AwsS3Config { - - @Value("${cloud.aws.credentials.access-key}") - private String accessKey; - - @Value("${cloud.aws.credentials.secret-key}") - private String secretKey; - - @Value("${cloud.aws.region.static}") - private String region; - - @Bean - public AmazonS3Client amazonS3Client() { - BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); - return (AmazonS3Client)AmazonS3ClientBuilder.standard() - .withRegion(region) - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) - .build(); - } - -} diff --git a/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java b/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java deleted file mode 100644 index 20d9abf0..00000000 --- a/infra-aws/src/main/java/com/example/infrastorage/s3/S3Service.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.example.infrastorage.s3; - -import java.io.InputStream; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.ObjectMetadata; - -import io.github.resilience4j.bulkhead.annotation.Bulkhead; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class S3Service { - private final AmazonS3Client amazonS3Client; - - @Value("${cloud.aws.s3.bucket}") - private String bucket; - - public record S3UploadResult(String key, String url) { - } - - @Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL) - @Async("s3UploadExecutor") - public CompletableFuture upload(String type, Long refId, MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) - try (InputStream inputStream = file.getInputStream()) { - String key = createFileKey(type, refId, file.getOriginalFilename()); - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(file.getSize()); - - amazonS3Client.putObject(bucket, key, inputStream, metadata); - String url = amazonS3Client.getUrl(bucket, key).toString(); - - return CompletableFuture.completedFuture(new S3UploadResult(key, url)); - } catch (Exception e) { - throw new RuntimeException("S3 업로드 실패", e); - } - } - - public void delete(String filename) { - try { - amazonS3Client.deleteObject(bucket, filename); - } catch (Exception e) { - throw new RuntimeException("S3 파일 삭제 실패", e); - } - } - - private String createFileKey(String type, Long refId, String filename) { - return type + "/" + refId + "/" + UUID.randomUUID() + "-" + filename; - } -} diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 11db36d5..f6f6a509 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -2,12 +2,12 @@ plugins { id 'java' } -bootJar { - enabled = true -} jar { enabled = false } +bootJar { + enabled = true +} group = 'com.nowait' version = '0.0.1-SNAPSHOT' @@ -16,10 +16,9 @@ repositories { mavenCentral() } -// api-admin에서 사용하는 도메인, 인프라 모듈만 추가 dependencies { implementation project(':nowait-common') - implementation project(':nowait-infra') // aws 관련 도메인 + implementation project(':nowait-infra') implementation project(':nowait-domain:domain-admin-rdb') implementation project(':nowait-domain:domain-core-rdb') diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index 47adf831..6e3a1302 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -2,12 +2,12 @@ plugins { id 'java' } -bootJar { - enabled = true -} jar { enabled = false } +bootJar { + enabled = true +} group = 'com.nowait' version = '0.0.1-SNAPSHOT' @@ -30,7 +30,6 @@ dependencies { // SPRING SECURITY implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // jwt implementation 'io.jsonwebtoken:jjwt-api:0.12.3' diff --git a/nowait-common/build.gradle b/nowait-common/build.gradle index 41106356..c2f76e1b 100644 --- a/nowait-common/build.gradle +++ b/nowait-common/build.gradle @@ -1,9 +1,6 @@ jar { enabled = true } -bootJar { - enabled = false -} java { toolchain { diff --git a/nowait-domain/build.gradle b/nowait-domain/build.gradle index ebc5b4ae..9ce9a13f 100644 --- a/nowait-domain/build.gradle +++ b/nowait-domain/build.gradle @@ -5,9 +5,6 @@ plugins { jar { enabled = true } -bootJar { - enabled = false -} group = 'com.nowaiting' @@ -32,10 +29,11 @@ dependencies { api 'jakarta.persistence:jakarta.persistence-api:3.1.0' // SPRING SECURITY api 'org.springframework.boot:spring-boot-starter-security' - api 'org.springframework.boot:spring-boot-starter-test' // Lombok (optional) compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/nowait-domain/domain-admin-rdb/build.gradle b/nowait-domain/domain-admin-rdb/build.gradle index 48aa299a..6b1b69c7 100644 --- a/nowait-domain/domain-admin-rdb/build.gradle +++ b/nowait-domain/domain-admin-rdb/build.gradle @@ -5,9 +5,6 @@ plugins { jar { enabled = true } -bootJar { - enabled = false -} group = 'com.nowaiting' diff --git a/nowait-domain/domain-core-rdb/build.gradle b/nowait-domain/domain-core-rdb/build.gradle index 7ea55f16..01bc023b 100644 --- a/nowait-domain/domain-core-rdb/build.gradle +++ b/nowait-domain/domain-core-rdb/build.gradle @@ -5,9 +5,6 @@ plugins { jar { enabled = true } -bootJar { - enabled = false -} group = 'com.nowait' @@ -32,9 +29,11 @@ dependencies { // SPRING SECURITY implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-validation' + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' diff --git a/nowait-domain/domain-user-rdb/build.gradle b/nowait-domain/domain-user-rdb/build.gradle index 09aa35f3..5a14ae53 100644 --- a/nowait-domain/domain-user-rdb/build.gradle +++ b/nowait-domain/domain-user-rdb/build.gradle @@ -5,9 +5,6 @@ plugins { jar { enabled = true } -bootJar { - enabled = false -} group = 'com.nowaiting' @@ -35,6 +32,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' diff --git a/nowait-infra/build.gradle b/nowait-infra/build.gradle index aa478c84..5cbc1f17 100644 --- a/nowait-infra/build.gradle +++ b/nowait-infra/build.gradle @@ -5,9 +5,6 @@ plugins { jar { enabled = true } -bootJar { - enabled = false -} group = 'com.nowaiting' From 2615a2bacafa598a980d23fae1df97f64c9f8735 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:42:08 +0900 Subject: [PATCH 092/685] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-domain/domain-admin-rdb/build.gradle | 2 +- .../com/nowait/auth/config/CorsConfig.java | 28 ----- .../nowait/auth/config/SecurityConfig.java | 93 --------------- .../com/nowait/auth/dto/CustomOAuth2User.java | 55 --------- .../com/nowait/auth/dto/KaKaoResponse.java | 40 ------- .../com/nowait/auth/dto/OAuth2Response.java | 19 --- .../auth/jwt/JwtAuthorizationFilter.java | 98 ---------------- .../java/com/nowait/auth/jwt/JwtUtil.java | 76 ------------ .../auth/oauth2/CustomOAuth2UserService.java | 70 ------------ .../oauth2/OAuth2LoginSuccessHandler.java | 80 ------------- .../auth/service/CustomUserDetailService.java | 30 ----- .../nowait/exception/BusinessException.java | 14 --- .../com/nowait/exception/ErrorMessage.java | 22 ---- .../com/nowait/exception/ErrorResponse.java | 26 ----- .../exception/GlobalExceptionHandler.java | 108 ------------------ .../RefreshTokenNotFoundException.java | 9 -- .../exception/ResourceNotFoundException.java | 14 --- .../exception/TokenBadRequestException.java | 8 -- .../exception/UnauthorizedException.java | 19 --- 19 files changed, 1 insertion(+), 810 deletions(-) delete mode 100644 security-front/src/main/java/com/nowait/auth/config/CorsConfig.java delete mode 100644 security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java delete mode 100644 security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java delete mode 100644 security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java delete mode 100644 security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java delete mode 100644 security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java delete mode 100644 security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java delete mode 100644 security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java delete mode 100644 security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java delete mode 100644 security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java delete mode 100644 security-front/src/main/java/com/nowait/exception/BusinessException.java delete mode 100644 security-front/src/main/java/com/nowait/exception/ErrorMessage.java delete mode 100644 security-front/src/main/java/com/nowait/exception/ErrorResponse.java delete mode 100644 security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java delete mode 100644 security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java delete mode 100644 security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java delete mode 100644 security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java delete mode 100644 security-front/src/main/java/com/nowait/exception/UnauthorizedException.java diff --git a/nowait-domain/domain-admin-rdb/build.gradle b/nowait-domain/domain-admin-rdb/build.gradle index 6b1b69c7..bf111954 100644 --- a/nowait-domain/domain-admin-rdb/build.gradle +++ b/nowait-domain/domain-admin-rdb/build.gradle @@ -7,7 +7,7 @@ jar { } -group = 'com.nowaiting' +group = 'com.nowait' version = rootProject.version java { diff --git a/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java b/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java deleted file mode 100644 index 34b293b5..00000000 --- a/security-front/src/main/java/com/nowait/auth/config/CorsConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.nowait.auth.config; - -import java.util.List; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -@Configuration -public class CorsConfig { - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - - config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:8083")); // 허용할 출처 설정 - config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 - config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 - config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - // config.setAllowCredentials(true); // 쿠키 포함 허용 - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 - return source; - } -} diff --git a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java b/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java deleted file mode 100644 index c4ebb970..00000000 --- a/security-front/src/main/java/com/nowait/auth/config/SecurityConfig.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.nowait.auth.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfigurationSource; - -import com.nowait.auth.jwt.JwtAuthorizationFilter; -import com.nowait.auth.jwt.JwtUtil; -import com.nowait.auth.oauth2.CustomOAuth2UserService; -import com.nowait.auth.oauth2.OAuth2LoginSuccessHandler; -import com.nowait.auth.service.CustomUserDetailService; - -import lombok.RequiredArgsConstructor; - -@Configuration -@EnableWebSecurity // security 활성화 어노테이션 -@RequiredArgsConstructor -public class SecurityConfig { - private final CustomOAuth2UserService customOAuth2UserService; - private final OAuth2LoginSuccessHandler OAuth2LoginSuccessHandler; - private final JwtUtil jwtUtil; - private final CustomUserDetailService userDetailsService; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) - .csrf(AbstractHttpConfigurer::disable) - // 시큐리티 폼 로그인 비활성화 - .formLogin(AbstractHttpConfigurer::disable) - // HTTP Basic 인증 비활성화 - .httpBasic(AbstractHttpConfigurer::disable) - // oauth2 로그인 - // - userInfoEndPoint에서 사용자 정보 불러오고, - // - successHandler에서 로그인 성공 시 JWT 생성 및 반환로직 - .oauth2Login(oauth2 -> - oauth2.userInfoEndpoint(userInfoEndpoint -> - userInfoEndpoint.userService(customOAuth2UserService) - ).successHandler(OAuth2LoginSuccessHandler) - ) - // 세션 사용하지 않음 - .sessionManagement(session -> - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) - .authorizeHttpRequests(auth -> auth - .requestMatchers( - "/oauth2/authorization/kakao", // 카카오 로그인 요청 - "/login/oauth2/code/**", // 카카오 인증 콜백 - "/api/refresh-token", // refresh token (토큰 갱신) - "/orders/**", // 주문 관련 API - "/v1/menus/**", - "/v1/stores/**", - "/api/users/signup", - "/api/users/login", - "/swagger-ui/**", - "/v3/api-docs/**", - "/v3/api-docs.json", - "/api-docs/**", - "/swagger-resources/**", - "/webjars/**", - "/demo-ui.html" - ) - .permitAll() - .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 - ) - // JWTFiler - .addFilterBefore(new JwtAuthorizationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - @Bean - public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); - authenticationProvider.setUserDetailsService(userDetailsService); - authenticationProvider.setPasswordEncoder(passwordEncoder()); - return authenticationProvider; - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java b/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java deleted file mode 100644 index 61432393..00000000 --- a/security-front/src/main/java/com/nowait/auth/dto/CustomOAuth2User.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.nowait.auth.dto; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.core.user.OAuth2User; - -import com.nowait.user.entity.User; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class CustomOAuth2User implements OAuth2User { - private final User user; - - @Override - public Map getAttributes() { - return Map.of("email", user.getEmail(), "nickname", user.getNickname()); - } - - // 사용자가 가지는 권한 설정 - @Override - public Collection getAuthorities() { - Collection authorities = new ArrayList<>(); - - authorities.add(new GrantedAuthority() { - @Override - public String getAuthority() { - return user.getRole().getName(); // 유저의 권한 리턴 - } - }); - - return authorities; - } - - @Override - public String getName() { - return user.getEmail(); - } - - public User getUser() { - return user; - } - - public Long getUserId() { - return user.getId(); - } - - public String getNickname() { - return user.getNickname(); - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java b/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java deleted file mode 100644 index e745bd26..00000000 --- a/security-front/src/main/java/com/nowait/auth/dto/KaKaoResponse.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.nowait.auth.dto; - -import java.util.Map; - -import lombok.RequiredArgsConstructor; - -// 카카오 OAuth2 응답에서 필요한 정보를 추출하는 역할 -@RequiredArgsConstructor -public class KaKaoResponse implements OAuth2Response { - private final Map attributes; - - @Override - public String getProvider() { - return "kakao"; - } - - @Override - public String getProviderId() { - return attributes.get("id").toString(); - } - - @Override - public String getEmail() { - Map kakaoAccount = (Map)attributes.get("kakao_account"); - return kakaoAccount.get("email").toString(); - } - - @Override - public String getNickName() { - Map properties = (Map)attributes.get("properties"); - return properties.get("nickname").toString(); - } - - @Override - public String getProfileImage() { - Map properties = (Map)attributes.get("properties"); - return properties.get("profile_image").toString(); - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java b/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java deleted file mode 100644 index de987480..00000000 --- a/security-front/src/main/java/com/nowait/auth/dto/OAuth2Response.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.nowait.auth.dto; - -public interface OAuth2Response { - // 제공자 (ex. naver, kakao) - String getProvider(); - - // 제공자에서 발급해주는 아이디 (번호) - String getProviderId(); - - // 아래 이메일, 닉네임, 프로필이미지는 내가 카카오 developers에서 발급받겠다고 신청한 정보들이다 - // 이메일 - String getEmail(); - - // 닉네임 - String getNickName(); - - // 프로필이미지 - String getProfileImage(); -} diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java b/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java deleted file mode 100644 index dcbbd985..00000000 --- a/security-front/src/main/java/com/nowait/auth/jwt/JwtAuthorizationFilter.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.nowait.auth.jwt; - -import java.io.IOException; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; - -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.common.enums.Role; -import com.nowait.common.enums.SocialType; -import com.nowait.user.entity.User; - -import io.jsonwebtoken.ExpiredJwtException; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -// JWT 검증 필터 -// 1. 헤더에서 accessToken 추출, 2. 토큰 검증, 3. 유효하면 사용자정보를 SecurityContextHolder에 세팅 -// 그러면, 이후 컨트롤러에서 @AuthenticationPrincipal에서 저장했던 사용자 정보를 꺼내쓸 수 있음 -@RequiredArgsConstructor -@Slf4j -public class JwtAuthorizationFilter extends OncePerRequestFilter { - private final JwtUtil jwtUtil; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - String header = request.getHeader("Authorization"); - - // 인증헤더 Bearer가 없다면, 다음 필터로 넘김 - if (header == null || !header.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - - log.debug("JwtAuthorizationFilter: Authorization 헤더가 없거나 Bearer 토큰 형식이 아님. JWT 인증 필터를 건너뜁니다. [header={}] ", header); - return; - } - - log.info("header :: {}, header.substring(7) :: {}", header, header.substring(7)); - String accessToken = header.substring(7); - - // 토큰 만료 여부 확인, 만료 시 다음 필터로 넘기지 않음 - try { - jwtUtil.isExpired(accessToken); - } catch (ExpiredJwtException e) { - - // response status code + msg - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().print("access token expired"); - - log.warn("JwtAuthorizationFilter: 만료된 AccessToken입니다. 토큰 인증 거부, URI: {}", request.getRequestURI()); - return; - } - - // 토큰이 accessToken 종류인지 확인 - String tokenCategory = jwtUtil.getTokenCategory(accessToken); - - if (!tokenCategory.equals("accessToken")) { - //response status code - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().print("invalid access token"); - - log.warn("JwtAuthorizationFilter: 잘못된 토큰 유형(accessToken 아님)으로 인증 요청. URI: {}, tokenCategory: {}", request.getRequestURI(), tokenCategory); - return; - } - - // userId와 role 값 추출 - Long userId = jwtUtil.getUserId(accessToken); - String roleString = jwtUtil.getRole(accessToken); - if (userId == null || roleString == null) { - log.warn("JwtAuthorizationFilter: JWT에서 userId 또는 role 추출 실패. 토큰: {}", accessToken); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().print("invalid token"); - return; - } - User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg" - , SocialType.KAKAO, Role.fromString(roleString)); - - CustomOAuth2User customOAuth2User = new CustomOAuth2User(user); - - // 스프링 시큐리티 인증 토큰 생성 - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( - customOAuth2User, null, customOAuth2User.getAuthorities()); - - // 생성한 인증 정보를 SecurityContext에 설정 - SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); - - log.info("JwtAuthorizationFilter: 인증 성공. userId={}, role={}, URI={}", userId, roleString, request.getRequestURI()); - - filterChain.doFilter(request, response); - - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java b/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java deleted file mode 100644 index fd0620e9..00000000 --- a/security-front/src/main/java/com/nowait/auth/jwt/JwtUtil.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.nowait.auth.jwt; - -import java.nio.charset.StandardCharsets; -import java.util.Date; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import io.jsonwebtoken.Jwts; - -@Component -public class JwtUtil { - private final SecretKey secretKey; - - // 시크릿 키를 암호화하여, 키 생성 - public JwtUtil(@Value("${jwt.secret}") String secret) { - this.secretKey = new SecretKeySpec( - secret.getBytes(StandardCharsets.UTF_8), - Jwts.SIG.HS256.key().build().getAlgorithm() - ); - } - - public String createAccessToken(String tokenCategory, Long userId, String role, Long expiredMs) { - return Jwts.builder() - .claim("tokenCategory", tokenCategory) // accessToken - .claim("userId", userId) - .claim("role", role) - .issuedAt(new Date(System.currentTimeMillis())) - .expiration(new Date(System.currentTimeMillis() + expiredMs)) - .signWith(secretKey) - .compact(); - } - - public String createRefreshToken(String tokenCategory, Long userId, Long expiredMs) { - return Jwts.builder() - .claim("tokenCategory", tokenCategory) // refreshToken - .claim("userId", userId) - .issuedAt(new Date(System.currentTimeMillis())) - .expiration(new Date(System.currentTimeMillis() + expiredMs)) - .signWith(secretKey) - .compact(); - } - - public String getTokenCategory(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseClaimsJws(token) - .getBody() - .get("tokenCategory", String.class); - } - - public String getRole(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseClaimsJws(token) - .getBody() - .get("role", String.class); - } - - public Long getUserId(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseClaimsJws(token) - .getBody() - .get("userId", Long.class); - } - - public Boolean isExpired(String token) { - return Jwts.parser().verifyWith(secretKey).build() - .parseSignedClaims(token) - .getPayload() - .getExpiration() - .before(new Date()); - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java b/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java deleted file mode 100644 index b51577f7..00000000 --- a/security-front/src/main/java/com/nowait/auth/oauth2/CustomOAuth2UserService.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.nowait.auth.oauth2; - -import java.util.Optional; - -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; - -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.auth.dto.KaKaoResponse; -import com.nowait.auth.dto.OAuth2Response; -import com.nowait.common.enums.Role; -import com.nowait.common.enums.SocialType; -import com.nowait.user.entity.User; -import com.nowait.user.repository.UserRepository; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -// OAuth2 제공자(카카오)로부터 제공받은 사용자 정보를, 우리 서비스에 맞게 가공, 변환 -@Service -@RequiredArgsConstructor -@Slf4j -public class CustomOAuth2UserService extends DefaultOAuth2UserService { - private final UserRepository userRepository; - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); - - log.info("CustomOAuth2UserService :: {}", oAuth2User); - log.info("oAuthUser.getAttributes :: {}", oAuth2User.getAttributes()); - - String registrationId = userRequest.getClientRegistration().getRegistrationId(); - OAuth2Response oAuth2Response = null; - - if (registrationId.equals("kakao")) { - oAuth2Response = new KaKaoResponse(oAuth2User.getAttributes()); - } else { - throw new OAuth2AuthenticationException("지원하지 않는 OAuth2 Provider 입니다."); - } - - // DB에 유저가 있는지 판단 - Optional foundUser = userRepository.findByEmail(oAuth2Response.getEmail()); - - // DB에 유저 없으면 - 회원가입 - if (foundUser.isEmpty()) { - - User user = User.builder() - .email(oAuth2Response.getEmail()) - .nickname(oAuth2Response.getNickName()) - .profileImage(oAuth2Response.getProfileImage()) - .socialType(SocialType.KAKAO) - .role(Role.USER) // 일반 유저 설정 - .build(); - - userRepository.save(user); - - return new CustomOAuth2User(user); - } else { - // DB에 유저 존재하면 - 로그인 진행 (이때 로그인 처리는 안하고, OAuth2LoginSuccessHandler에서 담당함) - User user = foundUser.get(); - - return new CustomOAuth2User(user); - } - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java b/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java deleted file mode 100644 index 9364e1cc..00000000 --- a/security-front/src/main/java/com/nowait/auth/oauth2/OAuth2LoginSuccessHandler.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.nowait.auth.oauth2; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import com.example.domaintoken.entity.Token; -import com.example.domaintoken.repository.TokenRepository; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nowait.auth.dto.CustomOAuth2User; -import com.nowait.auth.jwt.JwtUtil; -import com.nowait.user.entity.User; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -// 카카오 로그인 성공 시, 콜백 핸들러 -// 1. JWT 토큰 발급 -// - 이때, JWT payload는 보안상 최소한의 정보(userId, role)만 담겠다 -// 2. refreshToken만 DB에 저장 -// 3. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. -@Component -@RequiredArgsConstructor -@Slf4j -public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final JwtUtil jwtUtil; - private final TokenRepository tokenRepository; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - - // 1. CustomOAuth2UserService에서 설정한 OAuth2User 정보 가져오기 - CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); - - User user = customUserDetails.getUser(); - Long userId = customUserDetails.getUserId(); - String email = customUserDetails.getName(); - - Collection authorities = authentication.getAuthorities(); - Iterator iterator = authorities.iterator(); - GrantedAuthority auth = iterator.next(); - - String role = auth.getAuthority(); - - log.info("user, userId, email, role :: {} {} {} {}", user, userId, email, role); - - // 2. 1)의 사용자 정보를 담아, accessToken과 refreshToken 발행 - String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 60 * 60 * 1000L); // 유효기간 1시간 - String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, - 30 * 24 * 60 * 60 * 1000L); // 유효기간 30일 - - // 3. refreshToken을 DB에 저장 - Token refreshTokenEntity = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(refreshTokenEntity); - - // 4. JSON 응답으로, accessToken과 refreshToken 을 반환해준다. - response.setContentType("application/json"); - response.setCharacterEncoding("utf-8"); - - ObjectMapper objectMapper = new ObjectMapper(); // 객체 -> json 문자열로 변환 - String body = objectMapper.writeValueAsString( - Map.of( - "accessToken", accessToken, - "refreshToken", refreshToken - ) - ); - response.getWriter().write(body); - } - -} diff --git a/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java b/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java deleted file mode 100644 index 4476c9c4..00000000 --- a/security-front/src/main/java/com/nowait/auth/service/CustomUserDetailService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.nowait.auth.service; - -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Component; - -import com.nowait.user.entity.MemberDetails; -import com.nowait.user.entity.User; -import com.nowait.user.repository.UserRepository; - -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class CustomUserDetailService implements UserDetailsService { - - private final UserRepository userRepository; - - @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - User user = userRepository.findByEmail(email).orElseThrow(); - return MemberDetails.create(user); - } - - public UserDetails loadUserById(Long id) throws UsernameNotFoundException { - User member = userRepository.findById(id).orElseThrow(); - return MemberDetails.create(member); - } -} diff --git a/security-front/src/main/java/com/nowait/exception/BusinessException.java b/security-front/src/main/java/com/nowait/exception/BusinessException.java deleted file mode 100644 index 60eec165..00000000 --- a/security-front/src/main/java/com/nowait/exception/BusinessException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.nowait.exception; - -public abstract class BusinessException extends RuntimeException { - private final ErrorMessage errorMessage; - - protected BusinessException(ErrorMessage errorMessage) { - super(errorMessage.getMessage()); - this.errorMessage = errorMessage; - } - - public String getCode() { - return errorMessage.getCode(); - } -} diff --git a/security-front/src/main/java/com/nowait/exception/ErrorMessage.java b/security-front/src/main/java/com/nowait/exception/ErrorMessage.java deleted file mode 100644 index c365a92e..00000000 --- a/security-front/src/main/java/com/nowait/exception/ErrorMessage.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.nowait.exception; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ErrorMessage { - // global - INVALID_INPUT_VALUE("입력값이 올바르지 않습니다.", "global001"), - - // auth - UNAUTHORIZED("권한이 없습니다", "auth001"), - - // token - REFRESH_TOKEN_NOT_FOUND("기존 리프레시 토큰을 찾을 수 없습니다.", "token001"), - DOES_NOT_MATCH_REFRESH_TOKEN("기존 리프레시 토큰이 일치하지 않습니다.", "token002"); - - private final String message; - private final String code; - -} diff --git a/security-front/src/main/java/com/nowait/exception/ErrorResponse.java b/security-front/src/main/java/com/nowait/exception/ErrorResponse.java deleted file mode 100644 index a15935e8..00000000 --- a/security-front/src/main/java/com/nowait/exception/ErrorResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.nowait.exception; - -import java.util.HashMap; -import java.util.Map; - -import lombok.Getter; - -@Getter -public class ErrorResponse { - private final String message; - private final String code; - private final Map errors; - - public ErrorResponse(String message, String code) { - this.message = message; - this.code = code; - errors = new HashMap<>(); - } - - public ErrorResponse(String message, String code, Map errors) { - this.message = message; - this.code = code; - this.errors = errors; - } - -} diff --git a/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java b/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java deleted file mode 100644 index b9f7c1e2..00000000 --- a/security-front/src/main/java/com/nowait/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.nowait.exception; - -import static com.nowait.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.*; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; - -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.validation.FieldError; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingRequestValueException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.multipart.MultipartException; - -import io.swagger.v3.oas.annotations.Hidden; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Hidden -@RestControllerAdvice -public class GlobalExceptionHandler { - - // OAUTH 인증 실패 에러처리 메서드 - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(OAuth2AuthenticationException.class) - public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e) { - log.error("handleOAuth2AuthenticationException", e); - - return new ErrorResponse("OAuth 인증 실패 : " + e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(BusinessException.class) - public ErrorResponse handleBusinessException(BusinessException e) { - log.error("handleBusinessException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MethodArgumentNotValidException.class) - public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - log.error("handleMethodArgumentNotValidException", e); - Map errors = getErrors(e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(HttpMessageNotReadableException.class) - public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - log.error("handleHttpMessageNotReadableException", e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(IllegalArgumentException.class) - public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { - log.error("handleIllegalArgumentException", e); - return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MissingRequestValueException.class) - public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { - log.error("handleMissingRequestValueExceptionException", e); - return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - @ResponseStatus(value = UNAUTHORIZED) - @ExceptionHandler(UnauthorizedException.class) - public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { - log.error("handleUnauthorizedExceptionException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = NOT_FOUND) - @ExceptionHandler(ResourceNotFoundException.class) - public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { - log.error("handleResourceNotFoundExceptionException", e); - return new ErrorResponse(e.getMessage(), e.getCode()); - } - - @ResponseStatus(value = BAD_REQUEST) - @ExceptionHandler(MultipartException.class) - public ErrorResponse handleMultipartException(MultipartException e) { - log.error("handleMultipartException", e); - return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); - } - - - private static Map getErrors(MethodArgumentNotValidException e) { - return e.getBindingResult() - .getAllErrors() - .stream() - .filter(ObjectError.class::isInstance) - .collect(Collectors.toMap( - error -> error instanceof FieldError ? ((FieldError)error).getField() : error.getObjectName(), - ObjectError::getDefaultMessage, - (msg1, msg2) -> msg1 + ";" + msg2 - )); - } - -} diff --git a/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java b/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java deleted file mode 100644 index 5365ef52..00000000 --- a/security-front/src/main/java/com/nowait/exception/RefreshTokenNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nowait.exception; - -public class RefreshTokenNotFoundException extends ResourceNotFoundException { - - public RefreshTokenNotFoundException() { - super(ErrorMessage.REFRESH_TOKEN_NOT_FOUND); - } - -} diff --git a/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java b/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java deleted file mode 100644 index ae4e4b7b..00000000 --- a/security-front/src/main/java/com/nowait/exception/ResourceNotFoundException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.nowait.exception; - -public abstract class ResourceNotFoundException extends RuntimeException { - private final ErrorMessage errorMessage; - - protected ResourceNotFoundException(ErrorMessage errorMessage) { - super(errorMessage.getMessage()); - this.errorMessage = errorMessage; - } - - public String getCode() { - return errorMessage.getCode(); - } -} diff --git a/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java b/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java deleted file mode 100644 index 504852ee..00000000 --- a/security-front/src/main/java/com/nowait/exception/TokenBadRequestException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.nowait.exception; - -public class TokenBadRequestException extends BusinessException { - public TokenBadRequestException() { - super(ErrorMessage.DOES_NOT_MATCH_REFRESH_TOKEN); - } - -} diff --git a/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java b/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java deleted file mode 100644 index 65a8fd0a..00000000 --- a/security-front/src/main/java/com/nowait/exception/UnauthorizedException.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.nowait.exception; - -public class UnauthorizedException extends RuntimeException { - private final ErrorMessage errorMessage; - - public UnauthorizedException() { - super(ErrorMessage.UNAUTHORIZED.getMessage()); - this.errorMessage = ErrorMessage.UNAUTHORIZED; - } - - public UnauthorizedException(ErrorMessage errorMessage) { - super(errorMessage.getMessage()); - this.errorMessage = errorMessage; - } - - public String getCode() { - return errorMessage.getCode(); - } -} From a2675b7c20261588d47f4259edd79bce5572a718 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:53:39 +0900 Subject: [PATCH 093/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user.yml | 90 ++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index 67b2f28f..2b7894a4 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -42,8 +42,94 @@ jobs: run: | mkdir -p nowait-app-user-api/src/main/resources echo "${{ secrets.APPLICATION_DEV_USER_YML }}" \ - | base64 --decode \ - > nowait-app-user-api/src/main/resources/nowait-app-user-api.yml + | base64 --decode \name: Dev User CI/CD + +on: + push: + branches: + - develop + paths: + - 'nowait-app-user-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-user-rdb/**' + - 'nowait-infra/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' + +env: + PROJECT_NAME: NoWait + DIVISION: user + AWS_REGION: ap-northeast-2 + AWS_S3_BUCKET: nowait-deploy-github-actions + AWS_CODE_DEPLOY_APPLICATION: nowaiting + AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-user + +jobs: + build-with-gradle: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: JDK 17 설치 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + + - name: gradlew에 실행 권한 부여 + run: chmod +x ./gradlew + + - name: Set YML + run: | + mkdir -p nowait-app-user-api/src/main/resources + echo "${{ secrets.APPLICATION_DEV_USER_YML }}" \ + | base64 --decode \ + > nowait-app-user-api/src/main/resources/application-user.yml + find nowait-app-user-api/src + + - name: Build User JAR + run: ./gradlew clean :nowait-app-user-api:bootJar -x test + + - name: Prepare deployment package + run: | + rm -rf deploy + mkdir -p deploy/scripts + cp scripts/start-user.sh scripts/stop-user.sh deploy/scripts/ + cp appspec-user.yml deploy/appspec.yml + cp nowait-app-user-api/build/libs/nowait-app-user-api-*.jar deploy/nowait-app-user-api.jar + cd deploy + zip -r ../NoWait-user-${GITHUB_SHA}.zip . + cd .. + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} + + - name: Upload to S3 + run: | + aws deploy push \ + --application-name "${{ env.AWS_CODE_DEPLOY_APPLICATION }}" \ + --ignore-hidden-files \ + --s3-location "s3://${{ env.AWS_S3_BUCKET }}/${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip" \ + --source deploy + + - name: Create CodeDeploy deployment + run: | + aws deploy create-deployment \ + --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ + --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=${{ env.PROJECT_NAME }}/${{ env.DIVISION }}/${{ github.sha }}.zip,bundleType=zip + + + + > nowait-app-user-api/src/main/resources/nowait-app-user-api.yml find nowait-app-user-api/src - name: Build User JAR From 80cf25e7b6ee4abd9e41f8af187fe4d72d799ddc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 22:55:54 +0900 Subject: [PATCH 094/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user.yml | 10 +++++----- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index 67b2f28f..0ec7183e 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -2,7 +2,7 @@ name: Dev User CI/CD on: push: - branches: + branches: - develop paths: - 'nowait-app-user-api/**' @@ -21,20 +21,20 @@ env: AWS_S3_BUCKET: nowait-deploy-github-actions AWS_CODE_DEPLOY_APPLICATION: nowaiting AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-user - + jobs: build-with-gradle: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - + - name: JDK 17 설치 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'zulu' - + - name: gradlew에 실행 권한 부여 run: chmod +x ./gradlew @@ -43,7 +43,7 @@ jobs: mkdir -p nowait-app-user-api/src/main/resources echo "${{ secrets.APPLICATION_DEV_USER_YML }}" \ | base64 --decode \ - > nowait-app-user-api/src/main/resources/nowait-app-user-api.yml + > nowait-app-user-api/src/main/resources/application-user.yml find nowait-app-user-api/src - name: Build User JAR diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 0c0207e5..4bca3861 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { } } + From 2591925ca1a3674b626cc6660f136e0f4bf15092 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 23:08:46 +0900 Subject: [PATCH 095/685] =?UTF-8?q?fix(Bookmark,user):=EC=98=A4=ED=83=80?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B5=AC=EC=B2=B4=EC=A0=81=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B2=98=EB=A6=AC=20-=20jwt=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=97=90=EB=9F=AC=20=EB=A1=9C=EA=B7=B8=20=EA=B5=AC?= =?UTF-8?q?=EC=B2=B4=ED=99=94=20-=20bookmark=20=EC=B0=B8=EC=A1=B0=EB=B9=84?= =?UTF-8?q?=EA=B5=90=EC=97=90=EC=84=9C=20=EA=B0=92=EB=B9=84=EA=B5=90?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/jwt/JwtAuthorizationFilter.java | 15 +++++++++++---- .../token/service/TokenService.java | 10 +++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java index 099f1cc2..03f5071f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java @@ -35,7 +35,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (header == null || !header.startsWith("Bearer ")) { filterChain.doFilter(request, response); - log.info("JwtAuthorizationFilter 1 "); + log.debug("JwtAuthorizationFilter: Authorization 헤더가 없거나 Bearer 토큰 형식이 아님. JWT 인증 필터를 건너뜁니다. [header={}] ", header); return; } @@ -51,7 +51,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().print("access token expired"); - log.info("JwtAuthorizationFilter 2 "); + log.warn("JwtAuthorizationFilter: 만료된 AccessToken 입니다. 토큰 인증 거부, URI: {}", request.getRequestURI()); return; } @@ -63,7 +63,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().print("invalid access token"); - log.info("JwtAuthorizationFilter 3 "); + log.warn("JwtAuthorizationFilter: 잘못된 토큰 유형(accessToken 아님)으로 인증 요청. URI: {}, tokenCategory: {}", request.getRequestURI(), tokenCategory); return; } @@ -71,6 +71,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse Long userId = jwtUtil.getUserId(accessToken); String roleString = jwtUtil.getRole(accessToken); + if (userId == null || roleString == null) { + log.warn("JwtAuthorizationFilter: JWT에서 userId 또는 role 추출 실패. 토큰: {}", accessToken); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().print("invalid token"); + return; + } + User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg" , SocialType.KAKAO, Role.fromString(roleString)); @@ -83,7 +90,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // 생성한 인증 정보를 SecurityContext에 설정 SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); - log.info("JwtAuthorizationFilter 4 "); + log.info("JwtAuthorizationFilter: 인증 성공. userId={}, role={}, URI={}", userId, roleString, request.getRequestURI()); filterChain.doFilter(request, response); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java index 4752de8e..0b023a15 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java @@ -22,29 +22,29 @@ public class TokenService { private final JwtUtil jwtUtil; @Transactional - public Boolean validateToken(String token, Long userId){ + public boolean validateToken(String token, Long userId){ // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. Optional savedToken = tokenRepository.findByUserId(userId); // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 if (savedToken.isEmpty()){ - log.info("여기에 걸렸니 ? -- 1 "); + log.debug("DB에 현재 userId에 대응되는 리프레시 토큰이 없습니다"); return false; } // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 if (!savedToken.get().getRefreshToken().equals(token)){ - log.info("여기에 걸렸니 ? -- 2 "); + log.debug("DB에 저장된 리프레시 토큰와 현재 전달받은 리프레시 토큰 일치하지 않습니다"); return false; } // 리프레시 토큰의 만료여부 확인 if(jwtUtil.isExpired(token)){ - log.info("여기에 걸렸니 ? -- 3 "); + log.debug("리프레시 토큰이 만료된 토큰입니다"); return false; // 만료된 토큰은 유효하지 않음 } - log.info("여기에 걸렸니 ? -- 4 "); + log.info("리프레시 토큰이 유효한 토큰입니다"); return true; // 모든 조건 만족 시, 유효한 토큰 } From fc23a76f19b08330a4605dd621de9b376b9a5108 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 4 Jul 2025 23:14:22 +0900 Subject: [PATCH 096/685] =?UTF-8?q?refactor(order):=20TODO=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/domaincorerdb/order/entity/OrderStatus.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java index 98b50744..322a5218 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +// TODO 자바 코드로 변경 후 common 모듈로 이동 @Getter @RequiredArgsConstructor @Schema(description = "주문 상태 Enum") From df94b062e18479d13d6e1b958d1efd9cfa35e0b9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 6 Jul 2025 23:07:20 +0900 Subject: [PATCH 097/685] =?UTF-8?q?refactor:=20=EC=9E=AC=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 4bca3861..05f666ad 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -7,10 +7,8 @@ @EnableJpaAuditing @SpringBootApplication public class ApiUserApplication { - public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From d329794a6d56ab7b2eb162eb66da66ab0c142a5f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 6 Jul 2025 23:25:46 +0900 Subject: [PATCH 098/685] =?UTF-8?q?refactor:=20=EC=9E=AC=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/start-admin.sh | 2 +- scripts/start-user.sh | 2 +- scripts/stop-admin.sh | 2 +- scripts/stop-user.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/start-admin.sh b/scripts/start-admin.sh index 3fbec621..fbab278a 100644 --- a/scripts/start-admin.sh +++ b/scripts/start-admin.sh @@ -3,7 +3,7 @@ REPOSITORY="/home/ubuntu/spring-github-action-admin" cd $REPOSITORY -APP_NAME=application-admin +APP_NAME=nowait-app-admin-api JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) JAR_PATH=$REPOSITORY/$JAR_NAME diff --git a/scripts/start-user.sh b/scripts/start-user.sh index ad7dcafd..366e247b 100644 --- a/scripts/start-user.sh +++ b/scripts/start-user.sh @@ -3,7 +3,7 @@ REPOSITORY="/home/ubuntu/spring-github-action-user" cd $REPOSITORY -APP_NAME=application-user +APP_NAME=nowait-app-user-api JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) JAR_PATH=$REPOSITORY/$JAR_NAME diff --git a/scripts/stop-admin.sh b/scripts/stop-admin.sh index 7fcbe738..9c59f9ee 100644 --- a/scripts/stop-admin.sh +++ b/scripts/stop-admin.sh @@ -3,7 +3,7 @@ REPOSITORY="/home/ubuntu/spring-github-action-admin" cd $REPOSITORY -APP_NAME=application-admin +APP_NAME=nowait-app-admin-api JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) JAR_PATH=$REPOSITORY/$JAR_NAME STOP_LOG="$REPOSITORY/stop.log" diff --git a/scripts/stop-user.sh b/scripts/stop-user.sh index 115668ae..0b112c68 100644 --- a/scripts/stop-user.sh +++ b/scripts/stop-user.sh @@ -3,7 +3,7 @@ REPOSITORY="/home/ubuntu/spring-github-action-user" cd $REPOSITORY -APP_NAME=application-user +APP_NAME=nowait-app-user-api JAR_NAME=$(ls $REPOSITORY | grep '.jar' | tail -n 1) JAR_PATH=$REPOSITORY/$JAR_NAME STOP_LOG="$REPOSITORY/stop.log" From 8f305c6ee1673cf3b3637e2e821ec34762f1ffd9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 6 Jul 2025 23:28:35 +0900 Subject: [PATCH 099/685] =?UTF-8?q?refactor:=20=EC=9E=AC=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 05f666ad..07db6d34 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -10,5 +10,4 @@ public class ApiUserApplication { public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } - } From 5648efd89659a4860a9e1932ada7069313896636 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 7 Jul 2025 14:39:52 +0900 Subject: [PATCH 100/685] =?UTF-8?q?feat(Menu):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 본인 store외 접근 제한 로직 추가 - 예외처리 상세화 --- .../exception/GlobalExceptionHandler.java | 32 ++++++++++ .../menu/controller/MenuController.java | 26 +++++--- .../menu/service/MenuService.java | 63 +++++++++++++++---- .../user/dto/ManagerSignupRequestDto.java | 13 ++-- .../security/jwt/JwtAuthorizationFilter.java | 2 +- .../token/controller/TokenController.java | 2 +- .../nowait/common/exception/ErrorMessage.java | 6 +- .../MenuCreationUnauthorizedException.java | 9 +++ .../MenuDeleteUnauthorizedException.java | 9 +++ .../MenuUpdateUnauthorizedException.java | 9 +++ .../MenuViewUnauthorizedException.java | 9 +++ .../domaincorerdb/user/entity/User.java | 10 ++- 12 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCreationUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDeleteUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuUpdateUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuViewUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 68487e07..daa88858 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -21,6 +21,10 @@ import com.nowait.applicationadmin.security.exception.UnauthorizedException; import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; +import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; @@ -128,6 +132,34 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(MenuCreationUnauthorizedException.class) + public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e) { + log.error("menuCreationUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_CREATION_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(MenuViewUnauthorizedException.class) + public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e) { + log.error("menuViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(MenuUpdateUnauthorizedException.class) + public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedException e) { + log.error("menuUpdateUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_UPDATE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(MenuDeleteUnauthorizedException.class) + public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e) { + log.error("menuDeleteUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); + } + private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java index 08c6f1bd..e4ee6369 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -16,6 +17,7 @@ import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; import com.nowait.applicationadmin.menu.service.MenuService; import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -36,8 +38,9 @@ public class MenuController { @PostMapping("/create") @Operation(summary = "메뉴 생성", description = "새로운 메뉴를 생성합니다.") @ApiResponse(responseCode = "201", description = "메뉴 생성") - public ResponseEntity createMenu(@Valid @RequestBody MenuCreateRequest request) { - MenuCreateResponse response = menuService.createMenu(request); + public ResponseEntity createMenu(@Valid @RequestBody MenuCreateRequest request, + @AuthenticationPrincipal MemberDetails memberDetails) { + MenuCreateResponse response = menuService.createMenu(request,memberDetails); return ResponseEntity .status(HttpStatus.CREATED) @@ -51,12 +54,13 @@ public ResponseEntity createMenu(@Valid @RequestBody MenuCreateRequest reques @GetMapping("/all-menus/stores/{storeId}") @Operation(summary = "가게의 모든 메뉴 조회", description = "특정 가게의 모든 메뉴를 조회") @ApiResponse(responseCode = "200", description = "가게의 모든 메뉴 조회") - public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { + public ResponseEntity getMenusByStoreId(@PathVariable Long storeId, + @AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.getAllMenusByStoreId(storeId) + menuService.getAllMenusByStoreId(storeId,memberDetails) ) ); } @@ -66,13 +70,14 @@ public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { @ApiResponse(responseCode = "200", description = "메뉴 상세 조회") public ResponseEntity getMenuById( @PathVariable Long storeId, - @PathVariable Long menuId + @PathVariable Long menuId, + @AuthenticationPrincipal MemberDetails memberDetails ) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.getMenuById(storeId, menuId) + menuService.getMenuById(storeId, menuId,memberDetails) ) ); } @@ -83,13 +88,14 @@ public ResponseEntity getMenuById( @ApiResponse(responseCode = "200", description = "메뉴 수정") public ResponseEntity updateMenu( @PathVariable Long menuId, - @Valid @RequestBody MenuUpdateRequest request + @Valid @RequestBody MenuUpdateRequest request, + @AuthenticationPrincipal MemberDetails memberDetails ) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.updateMenu(menuId, request) + menuService.updateMenu(menuId, request,memberDetails) ) ); } @@ -97,12 +103,12 @@ public ResponseEntity updateMenu( @DeleteMapping("/delete/{menuId}") @Operation(summary = "메뉴 삭제", description = "특정 메뉴를 삭제합니다.") @ApiResponse(responseCode = "200", description = "메뉴 삭제") - public ResponseEntity deleteMenu(@PathVariable Long menuId) { + public ResponseEntity deleteMenu(@PathVariable Long menuId,@AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.deleteMenu(menuId) + menuService.deleteMenu(menuId,memberDetails) ) ); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 2ea282bf..5c31d45e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -11,12 +11,21 @@ import com.nowait.applicationadmin.menu.dto.MenuReadDto; import com.nowait.applicationadmin.menu.dto.MenuReadResponse; import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; +import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.entity.MenuImage; +import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; +import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; +import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; -import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -26,18 +35,31 @@ public class MenuService { private final MenuRepository menuRepository; private final MenuImageRepository menuImageRepository; + private final UserRepository userRepository; @Transactional - public MenuCreateResponse createMenu(MenuCreateRequest request) { - Menu toSave = request.toEntity(); + public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) { + // 사용자 정보 가져오기 + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(request.getStoreId())) { + throw new MenuCreationUnauthorizedException(); + } + // 메뉴 생성 로직 + Menu toSave = request.toEntity(); Menu saved = menuRepository.save(toSave); return MenuCreateResponse.fromEntity(saved); } @Transactional(readOnly = true) - public MenuReadResponse getAllMenusByStoreId(Long storeId) { + public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberDetails) { + // 사용자 정보 가져오기 + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + + // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 + validateMenuViewAuthorization(user, storeId); List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); List menuReadResponse = menus.stream() @@ -54,13 +76,16 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { } @Transactional(readOnly = true) - public MenuReadDto getMenuById(Long storeId, Long menuId) { + public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDetails) { if (storeId == null || menuId == null) { - throw new OrderParameterEmptyException(); + throw new MenuParamEmptyException(); } - + // 사용자 정보 가져오기 + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) .orElseThrow(MenuNotFoundException::new); + // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 + validateMenuViewAuthorization(user, menu.getStoreId()); List images = menuImageRepository.findByMenu(menu); List imageDto = images.stream() @@ -68,14 +93,19 @@ public MenuReadDto getMenuById(Long storeId, Long menuId) { .toList(); return MenuReadDto.fromEntity(menu, imageDto); - } + } @Transactional - public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { + public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Menu menu = menuRepository.findByIdAndDeletedFalse(menuId) .orElseThrow(MenuNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) { + throw new MenuUpdateUnauthorizedException(); + } + menu.updateInfo( request.getName(), request.getDescription(), @@ -93,10 +123,13 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) { } @Transactional - public String deleteMenu(Long menuId) { - Menu menu = menuRepository.findById(menuId) - .orElseThrow(MenuNotFoundException::new); + public String deleteMenu(Long menuId, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Menu menu = menuRepository.findById(menuId).orElseThrow(MenuNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) { + throw new MenuDeleteUnauthorizedException(); + } menu.markAsDeleted(); menuRepository.save(menu); @@ -113,4 +146,10 @@ public Boolean toggleSoldOut(Long menuId) { return menu.getIsSoldOut(); } + private static void validateMenuViewAuthorization(User user, Long storeId) { + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new MenuViewUnauthorizedException(); + } + } + } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java index cece8edd..1e8368fe 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,23 +19,26 @@ public class ManagerSignupRequestDto { @NotBlank @Email(regexp = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@" + "[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$") - @Schema(description = "이메일", example = "abc@gmail.com") + @Schema(description = "이메일(예시)", example = "abc@gmail.com") private String email; @NotBlank @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}") - @Schema(description = "비밀번호", example = "1234568!@") + @Schema(description = "비밀번호(예시)", example = "1234568!@") private String password; @NotBlank @Pattern(regexp = "^[a-zA-Z가-힣]{2,12}$") - @Schema(description = "닉네임", example = "가십이") + @Schema(description = "이름(예시)", example = "김노웻") private String nickname; - @NotBlank @Schema(description = "로그인타입", example = "LOCAL") private String socialType; + @NotNull + @Schema(description = "관리자가 속한 storeId", example = "1") + private Long storeId; + public User toEntity() { return User.builder() .email(email) @@ -42,6 +46,7 @@ public User toEntity() { .nickname(nickname) .socialType(SocialType.LOCAL) .role(Role.MANAGER) + .storeId(storeId) .build(); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java index 03f5071f..ca599ce8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java @@ -79,7 +79,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg" - , SocialType.KAKAO, Role.fromString(roleString)); + , SocialType.KAKAO, Role.fromString(roleString),5000L); CustomOAuth2User customOAuth2User = new CustomOAuth2User(user); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index 9a4be370..f1929331 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -42,7 +42,7 @@ public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ long currentAccessTokenExpiration = accessTokenExpiration; if (role.equals("SUPER_ADMIN")) { - currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일 + currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일 } // 리프레시 토큰 유효성 검증 diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index a0e33556..dab1e0b9 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -29,7 +29,11 @@ public enum ErrorMessage { // menu MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"), - MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu001"), + MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu002"), + MENU_CREATION_UNAUTHORIZED("메뉴 생성 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu003"), + MENU_VIEW_UNAUTHORIZED("메뉴 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu004"), + MENU_UPDATE_UNAUTHORIZED("메뉴 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu005"), + MENU_DELETE_UNAUTHORIZED("메뉴 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu006"), // store STORE_PARAMETER_EMPTY("주점 생성 시 파라미터 정보가 없습니다.", "store001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCreationUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCreationUnauthorizedException.java new file mode 100644 index 00000000..7d68bb9b --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCreationUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuCreationUnauthorizedException extends RuntimeException { + public MenuCreationUnauthorizedException() { + super(ErrorMessage.MENU_CREATION_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDeleteUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDeleteUnauthorizedException.java new file mode 100644 index 00000000..5da343f7 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDeleteUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuDeleteUnauthorizedException extends RuntimeException { + public MenuDeleteUnauthorizedException() { + super(ErrorMessage.MENU_DELETE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuUpdateUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuUpdateUnauthorizedException.java new file mode 100644 index 00000000..6cb4eeff --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuUpdateUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuUpdateUnauthorizedException extends RuntimeException { + public MenuUpdateUnauthorizedException() { + super(ErrorMessage.MENU_UPDATE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuViewUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuViewUnauthorizedException.java new file mode 100644 index 00000000..24e0f28b --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuViewUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuViewUnauthorizedException extends RuntimeException { + public MenuViewUnauthorizedException() { + super(ErrorMessage.MENU_VIEW_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java index 0bb965f4..86d45d9b 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java @@ -44,23 +44,29 @@ public class User { @Enumerated(EnumType.STRING) private Role role; + private Long storeId; + @Builder - public User(String email,String password, String nickname, String profileImage, SocialType socialType, Role role){ + public User(String email,String password, String nickname, String profileImage, SocialType socialType, + Role role, Long storeId) { this.email = email; this.password = password; this.nickname = nickname; this.profileImage = profileImage; this.socialType = socialType; this.role = role; + this.storeId = storeId; } - public static User createUserWithId(Long userId, String email, String nickname, String profileImage, SocialType socialType, Role role){ + public static User createUserWithId(Long userId, String email, String nickname, String profileImage, + SocialType socialType, Role role, Long storeId) { User user = User.builder() .email(email) .nickname(nickname) .profileImage(profileImage) .socialType(socialType) .role(role) + .storeId(storeId) .build(); user.id = userId; From 749ece37153c422d8c5b1f229940fe1410dfe785 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 7 Jul 2025 14:58:44 +0900 Subject: [PATCH 101/685] =?UTF-8?q?feat(Order):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 본인 store 외 접근 제한 로직 추가 - 예외처리 상세화 --- .../exception/GlobalExceptionHandler.java | 24 +++++++++++++++++++ .../order/controller/OrderController.java | 13 ++++++---- .../order/service/OrderService.java | 24 ++++++++++++++++--- .../nowait/common/exception/ErrorMessage.java | 3 +++ .../exception/OrderNotFoundException.java | 9 +++++++ .../OrderUpdateUnauthorizedException.java | 9 +++++++ .../OrderViewUnauthorizedException.java | 9 +++++++ 7 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderNotFoundException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderUpdateUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderViewUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index daa88858..a888d311 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -27,7 +27,10 @@ import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; +import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -125,6 +128,27 @@ public ErrorResponse duplicateOrderException(DuplicateOrderException e) { return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); } + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(OrderViewUnauthorizedException.class) + public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e) { + log.error("orderViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), ORDER_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = NOT_FOUND) + @ExceptionHandler(OrderNotFoundException.class) + public ErrorResponse orderNotFoundException(OrderNotFoundException e) { + log.error("orderNotFoundException", e); + return new ErrorResponse(e.getMessage(), ORDER_NOT_FOUND.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(OrderUpdateUnauthorizedException.class) + public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedException e) { + log.error("orderUpdateUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), ORDER_UPDATE_UNAUTHORIZED.getCode()); + } + @ResponseStatus(value = NOT_FOUND) @ExceptionHandler(ReservationNotFoundException.class) public ErrorResponse reservationNotFoundException(ReservationNotFoundException e) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index dfc03619..6c2b845d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -4,6 +4,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -16,6 +17,7 @@ import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.applicationadmin.order.service.OrderService; import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -34,8 +36,9 @@ public class OrderController { @GetMapping("/{storeId}") @Operation(summary = "주점별 주문리스트 조회", description = "특정 주점에 대한 예약리스트 조회") @ApiResponse(responseCode = "200", description = "주리스트 조회") - public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId) { - List response = orderService.findAllOrders(storeId); + public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId, + @AuthenticationPrincipal MemberDetails memberDetails) { + List response = orderService.findAllOrders(storeId,memberDetails); return ResponseEntity .status(HttpStatus.OK) .body( @@ -51,9 +54,11 @@ public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId) { @ApiResponse(responseCode = "400", description = "주문을 찾을 수 없음") public ResponseEntity updateOrderStatus( @PathVariable Long orderId, - @RequestBody@Valid OrderStatusUpdateRequestDto requestDto + @RequestBody@Valid OrderStatusUpdateRequestDto requestDto, + @AuthenticationPrincipal MemberDetails memberDetails ) { - OrderStatusUpdateResponseDto response = orderService.updateOrderStatus(orderId, requestDto.getOrderStatus()); + OrderStatusUpdateResponseDto response = orderService.updateOrderStatus( + orderId,requestDto.getOrderStatus(),memberDetails); return ResponseEntity .status(HttpStatus.OK) .body(ApiUtils.success(response)); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 41418531..beea5508 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -8,9 +8,17 @@ import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; +import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; +import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.order.repository.OrderRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -18,18 +26,28 @@ @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; + private final UserRepository userRepository; @Transactional(readOnly = true) - public List findAllOrders(Long storeId) { + public List findAllOrders(Long storeId, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new OrderViewUnauthorizedException(); + } return orderRepository.findAllByStore_StoreId(storeId).stream() .map(OrderResponseDto::fromEntity) .collect(Collectors.toList()); } @Transactional - public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus) { + public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus, + MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); UserOrder userOrder = orderRepository.findById(orderId) - .orElseThrow(() -> new IllegalArgumentException("Order not found with id: " + orderId)); + .orElseThrow(OrderNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { + throw new OrderUpdateUnauthorizedException(); + } userOrder.updateStatus(newStatus); return OrderStatusUpdateResponseDto.fromEntity(userOrder); } diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index dab1e0b9..538800de 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -19,6 +19,9 @@ public enum ErrorMessage { ORDER_ITEMS_EMPTY("주문 항목이 없습니다.", "order002"), DUPLICATE_ORDER("동일한 주문이 접수되었습니다.", "order003"), DEPOSITOR_NAME_TOO_LONG("주문자명은 10자 이내 글자열입니다.", "order004"), + ORDER_VIEW_UNAUTHORIZED("주문 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "order005"), + ORDER_NOT_FOUND("해당 주문을 찾을 수 없습니다.", "order006"), + ORDER_UPDATE_UNAUTHORIZED("주문 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "order007"), //reservation NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderNotFoundException.java new file mode 100644 index 00000000..935927bd --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class OrderNotFoundException extends RuntimeException { + public OrderNotFoundException() { + super(ErrorMessage.ORDER_NOT_FOUND.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderUpdateUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderUpdateUnauthorizedException.java new file mode 100644 index 00000000..9b9e8833 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderUpdateUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class OrderUpdateUnauthorizedException extends RuntimeException { + public OrderUpdateUnauthorizedException() { + super(ErrorMessage.ORDER_UPDATE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderViewUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderViewUnauthorizedException.java new file mode 100644 index 00000000..b623d5fa --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderViewUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class OrderViewUnauthorizedException extends RuntimeException { + public OrderViewUnauthorizedException() { + super(ErrorMessage.ORDER_VIEW_UNAUTHORIZED.getMessage()); + } +} From 29c7028be11d5cf683390eb80533ed9869f5617a Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 7 Jul 2025 15:08:03 +0900 Subject: [PATCH 102/685] =?UTF-8?q?feat(Reservation):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 본인 store 외 접근 제한 로직 추가 - 예외처리 상세화 --- .../exception/GlobalExceptionHandler.java | 16 ++++++++++++++ .../controller/ReservationController.java | 14 +++++++----- .../service/ReservationService.java | 22 +++++++++++++++++-- .../nowait/common/exception/ErrorMessage.java | 2 ++ ...eservationUpdateUnauthorizedException.java | 10 +++++++++ .../ReservationViewUnauthorizedException.java | 10 +++++++++ 6 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationUpdateUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationViewUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index a888d311..3361fcd9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -32,6 +32,8 @@ import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -156,6 +158,20 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(ReservationViewUnauthorizedException.class) + public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e) { + log.error("reservationViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(ReservationUpdateUnauthorizedException.class) + public ErrorResponse reservationUpdateUnauthorizedException(ReservationUpdateUnauthorizedException e) { + log.error("reservationUpdateUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_UPDATE_UNAUTHORIZED.getCode()); + } + @ResponseStatus(value = FORBIDDEN) @ExceptionHandler(MenuCreationUnauthorizedException.class) public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index b29d3b56..9868d04d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -14,6 +15,7 @@ import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; import com.nowait.applicationadmin.reservation.service.ReservationService; import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -31,8 +33,9 @@ public class ReservationController { @GetMapping("/admin/{storeId}") @Operation(summary = "주점별 예약리스트 조회", description = "특정 주점에 대한 예약리스트 조회") @ApiResponse(responseCode = "200", description = "예약리스트 조회") - public ResponseEntity getReservationListByStoreId(@PathVariable Long storeId) { - ReservationStatusSummaryDto response = reservationService.getReservationListByStoreId(storeId); + public ResponseEntity getReservationListByStoreId(@PathVariable Long storeId, + @AuthenticationPrincipal MemberDetails memberDetails) { + ReservationStatusSummaryDto response = reservationService.getReservationListByStoreId(storeId,memberDetails); return ResponseEntity .status(HttpStatus.OK) .body( @@ -45,9 +48,10 @@ public ResponseEntity getReservationListByStoreId(@PathVariable Long storeId) @PatchMapping("/admin/updates/{reservationId}") @Operation(summary = "예약팀 상태 변경", description = "특정 예약에 대한 상태 변경(예약중->호출중,호출중->입장완료,취소)") @ApiResponse(responseCode = "200", description = "예약팀 상태 변경") - public ResponseEntity updateReservationStatus(@PathVariable Long reservationId,@RequestBody - ReservationStatusUpdateRequestDto requestDto) { - CallGetResponseDto response = reservationService.updateReservationStatus(reservationId,requestDto); + public ResponseEntity updateReservationStatus(@PathVariable Long reservationId, + @RequestBody ReservationStatusUpdateRequestDto requestDto, + @AuthenticationPrincipal MemberDetails memberDetails) { + CallGetResponseDto response = reservationService.updateReservationStatus(reservationId,requestDto,memberDetails); return ResponseEntity .status(HttpStatus.OK) .body( diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 249f6b3f..74ffc0c1 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -11,9 +11,17 @@ import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -22,9 +30,14 @@ public class ReservationService { private final ReservationRepository reservationRepository; + private final UserRepository userRepository; @Transactional(readOnly = true) - public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId) { + public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new ReservationViewUnauthorizedException(); + } List reservations = reservationRepository.findAllByStore_StoreIdOrderByRequestedAtAsc(storeId); // 상태별 카운트 집계 @@ -50,9 +63,14 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId) { .build(); } @Transactional - public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto) { + public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, + MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Reservation reservation = reservationRepository.findById(reservationId) .orElseThrow(ReservationNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(reservation.getStore().getStoreId())) { + throw new ReservationUpdateUnauthorizedException(); + } reservation.updateStatus(requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 538800de..422010e8 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -25,6 +25,8 @@ public enum ErrorMessage { //reservation NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"), + RESERVATION_VIEW_UNAUTHORIZED("예약 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation002"), + RESERVATION_UPDATE_UNAUTHORIZED("예약 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation003"), // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationUpdateUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationUpdateUnauthorizedException.java new file mode 100644 index 00000000..f2af519b --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationUpdateUnauthorizedException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationUpdateUnauthorizedException extends RuntimeException { + public ReservationUpdateUnauthorizedException() { + super(ErrorMessage.RESERVATION_UPDATE_UNAUTHORIZED.getMessage()); + } +} + diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationViewUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationViewUnauthorizedException.java new file mode 100644 index 00000000..e5ff5220 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationViewUnauthorizedException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationViewUnauthorizedException extends RuntimeException { + public ReservationViewUnauthorizedException() { + super(ErrorMessage.RESERVATION_VIEW_UNAUTHORIZED.getMessage()); + } +} + From ade1737ab461ab3092a2b32b4441bf22221a8fd8 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 7 Jul 2025 15:13:09 +0900 Subject: [PATCH 103/685] =?UTF-8?q?feat(Reservation):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - user.getStoreId = null 일 가능성을 고려하여 nullable-false 추가 - NPE 방지 --- .../reservation/service/ReservationService.java | 3 +-- .../main/java/com/nowait/domaincorerdb/user/entity/User.java | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 74ffc0c1..cf61f493 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -66,8 +66,7 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Reservation reservation = reservationRepository.findById(reservationId) - .orElseThrow(ReservationNotFoundException::new); + Reservation reservation = reservationRepository.findById(reservationId).orElseThrow(ReservationNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(reservation.getStore().getStoreId())) { throw new ReservationUpdateUnauthorizedException(); } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java index 86d45d9b..c1502486 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java @@ -44,6 +44,7 @@ public class User { @Enumerated(EnumType.STRING) private Role role; + @Column(nullable = false) private Long storeId; @Builder From e988355ff34a6b1f841db2d707f413b4c886539d Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 7 Jul 2025 15:40:10 +0900 Subject: [PATCH 104/685] =?UTF-8?q?feat(Store):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - store RUD 시 접근 제한 로직 추가 - 비즈니스 로직 고려하여 user 생성 후 storeId 수동 추가 로직으로 변경 --- .../exception/GlobalExceptionHandler.java | 23 +++++++++++++ .../store/controller/StoreController.java | 15 +++++---- .../store/service/StoreService.java | 7 ++-- .../store/service/StoreServiceImpl.java | 32 +++++++++++++++---- .../user/dto/ManagerSignupRequestDto.java | 4 --- .../nowait/common/exception/ErrorMessage.java | 3 ++ .../StoreDeleteUnauthorizedException.java | 9 ++++++ .../StoreUpdateUnauthorizedException.java | 9 ++++++ .../StoreViewUnauthorizedException.java | 9 ++++++ .../domaincorerdb/user/entity/User.java | 1 - 10 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreDeleteUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreUpdateUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreViewUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 3361fcd9..b2cbf47f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -34,6 +34,9 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreDeleteUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreUpdateUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -200,6 +203,26 @@ public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedExcep return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); } + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(StoreViewUnauthorizedException.class) + public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e) { + log.error("storeViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(StoreUpdateUnauthorizedException.class) + public ErrorResponse storeUpdateUnauthorizedException(StoreUpdateUnauthorizedException e) { + log.error("storeUpdateUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_UPDATE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(value = FORBIDDEN) + @ExceptionHandler(StoreDeleteUnauthorizedException.class) + public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e) { + log.error("storeDeleteUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_DELETE_UNAUTHORIZED.getCode()); + } private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java index 07a5e63f..2d3a6aae 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -16,6 +17,7 @@ import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; import com.nowait.applicationadmin.store.service.StoreService; import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -52,12 +54,12 @@ public ResponseEntity createStore(@Valid @RequestBody StoreCreateRequest requ @GetMapping("/{storeId}") @Operation(summary = "주점 조회", description = "주점 ID로 주점을 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 조회 성공") - public ResponseEntity getStoreById(@PathVariable Long storeId) { + public ResponseEntity getStoreById(@PathVariable Long storeId,@AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - storeService.getStoreByStoreId(storeId) + storeService.getStoreByStoreId(storeId,memberDetails) ) ); } @@ -67,13 +69,14 @@ public ResponseEntity getStoreById(@PathVariable Long storeId) { @ApiResponse(responseCode = "200", description = "주점 정보 수정 성공") public ResponseEntity updateStore( @PathVariable Long storeId, - @Valid @RequestBody StoreUpdateRequest request + @Valid @RequestBody StoreUpdateRequest request, + @AuthenticationPrincipal MemberDetails memberDetails ) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - storeService.updateStore(storeId, request) + storeService.updateStore(storeId, request, memberDetails) ) ); } @@ -81,12 +84,12 @@ public ResponseEntity updateStore( @DeleteMapping("/{storeId}") @Operation(summary = "주점 삭제", description = "주점 ID로 주점을 삭제합니다.") @ApiResponse(responseCode = "200", description = "주점 삭제 성공") - public ResponseEntity deleteStore(@PathVariable Long storeId) { + public ResponseEntity deleteStore(@PathVariable Long storeId,@AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .ok() .body( ApiUtils.success( - storeService.deleteStore(storeId) + storeService.deleteStore(storeId,memberDetails) ) ); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java index 02c39915..5ae0dcdf 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java @@ -4,16 +4,17 @@ import com.nowait.applicationadmin.store.dto.StoreCreateResponse; import com.nowait.applicationadmin.store.dto.StoreReadDto; import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; +import com.nowait.domaincorerdb.user.entity.MemberDetails; public interface StoreService { StoreCreateResponse createStore(StoreCreateRequest request); - StoreReadDto getStoreByStoreId(Long storeId); + StoreReadDto getStoreByStoreId(Long storeId, MemberDetails memberDetails); - StoreReadDto updateStore(Long storeId, StoreUpdateRequest request); + StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, MemberDetails memberDetails); - String deleteStore(Long storeId); + String deleteStore(Long storeId, MemberDetails memberDetails); Boolean toggleActive(Long storeId); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index ce4f65da..0dec4fd8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -10,15 +10,22 @@ import com.nowait.applicationadmin.store.dto.StoreImageUploadResponse; import com.nowait.applicationadmin.store.dto.StoreReadDto; import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; +import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; +import com.nowait.domaincorerdb.store.exception.StoreDeleteUnauthorizedException; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; +import com.nowait.domaincorerdb.store.exception.StoreUpdateUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; import com.nowait.domaincorerdb.store.repository.StoreImageRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; - -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @Service @@ -27,6 +34,7 @@ public class StoreServiceImpl implements StoreService { private final StoreRepository storeRepository; private final StoreImageRepository storeImageRepository; + private final UserRepository userRepository; @Override @Transactional @@ -42,9 +50,12 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { @Override @Transactional(readOnly = true) - public StoreReadDto getStoreByStoreId(Long storeId) { + public StoreReadDto getStoreByStoreId(Long storeId, MemberDetails memberDetails) { if (storeId == null) throw new StoreParamEmptyException(); - + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StoreViewUnauthorizedException(); + } Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -58,9 +69,12 @@ public StoreReadDto getStoreByStoreId(Long storeId) { @Override @Transactional - public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request) { + public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (storeId == null || request == null) throw new StoreParamEmptyException(); - + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StoreUpdateUnauthorizedException(); + } Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -82,10 +96,14 @@ public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request) { @Override @Transactional - public String deleteStore(Long storeId) { + public String deleteStore(Long storeId, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (storeId == null) { throw new StoreParamEmptyException(); } + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StoreDeleteUnauthorizedException(); + } Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java index 1e8368fe..8b327e01 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java @@ -35,9 +35,6 @@ public class ManagerSignupRequestDto { @Schema(description = "로그인타입", example = "LOCAL") private String socialType; - @NotNull - @Schema(description = "관리자가 속한 storeId", example = "1") - private Long storeId; public User toEntity() { return User.builder() @@ -46,7 +43,6 @@ public User toEntity() { .nickname(nickname) .socialType(SocialType.LOCAL) .role(Role.MANAGER) - .storeId(storeId) .build(); } diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 422010e8..5a651f45 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -43,6 +43,9 @@ public enum ErrorMessage { // store STORE_PARAMETER_EMPTY("주점 생성 시 파라미터 정보가 없습니다.", "store001"), STORE_NOT_FOUND("해당 주점을 찾을 수 없습니다.", "store002"), + STORE_VIEW_UNAUTHORIZED("주점 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store003"), + STORE_UPDATE_UNAUTHORIZED("주점 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store004"), + STORE_DELETE_UNAUTHORIZED("주점 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store005"), // image IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreDeleteUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreDeleteUnauthorizedException.java new file mode 100644 index 00000000..9b92d807 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreDeleteUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreDeleteUnauthorizedException extends RuntimeException { + public StoreDeleteUnauthorizedException() { + super(ErrorMessage.STORE_DELETE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreUpdateUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreUpdateUnauthorizedException.java new file mode 100644 index 00000000..a77d8379 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreUpdateUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreUpdateUnauthorizedException extends RuntimeException { + public StoreUpdateUnauthorizedException() { + super(ErrorMessage.STORE_UPDATE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreViewUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreViewUnauthorizedException.java new file mode 100644 index 00000000..ee996974 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreViewUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreViewUnauthorizedException extends RuntimeException { + public StoreViewUnauthorizedException() { + super(ErrorMessage.STORE_VIEW_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java index c1502486..86d45d9b 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java @@ -44,7 +44,6 @@ public class User { @Enumerated(EnumType.STRING) private Role role; - @Column(nullable = false) private Long storeId; @Builder From baad7ff7492f29235956244b1fcbd137504a724e Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 14:14:36 +0900 Subject: [PATCH 105/685] =?UTF-8?q?feat(Order):=20order=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=B9=BC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 메뉴명 & 수량 추가 --- .../order/dto/OrderResponseDto.java | 13 +++++++++++++ .../order/service/OrderService.java | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java index f5e93610..232413ee 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -1,7 +1,11 @@ package com.nowait.applicationadmin.order.dto; import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import com.nowait.domaincorerdb.order.entity.OrderItem; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; @@ -16,15 +20,24 @@ public class OrderResponseDto { private String depositorName; private Integer totalPrice; private OrderStatus status; + private Map menuNamesAndQuantities; private LocalDateTime createdAt; public static OrderResponseDto fromEntity(UserOrder userOrder) { + Map menuNamesAndQuantities = userOrder.getOrderItems().stream() + .collect(Collectors.toMap( + orderItem -> orderItem.getMenu().getName(), + OrderItem::getQuantity, + Integer::sum // 메뉴명이 중복일 때 수량 합침 + )); + return OrderResponseDto.builder() .id(userOrder.getId()) .tableId(userOrder.getTableId()) .depositorName(userOrder.getDepositorName()) .totalPrice(userOrder.getTotalPrice()) .status(userOrder.getStatus()) + .menuNamesAndQuantities(menuNamesAndQuantities) .createdAt(userOrder.getCreatedAt()) .build(); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index beea5508..eebf249c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -9,6 +9,9 @@ import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; +import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; @@ -27,10 +30,12 @@ public class OrderService { private final OrderRepository orderRepository; private final UserRepository userRepository; + private final MenuRepository menuRepository; @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Menu menu = menuRepository.findById(storeId).orElseThrow(MenuNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new OrderViewUnauthorizedException(); } From 7e653023df9fcb09e052156554f4f8a8a659a579 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 14:25:40 +0900 Subject: [PATCH 106/685] =?UTF-8?q?feat(Order):=20order=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=B9=BC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 메뉴명 & 수량 추가 --- .../applicationadmin/order/service/OrderService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index eebf249c..ab54a7c8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -18,6 +18,9 @@ import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.order.repository.OrderRepository; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -30,12 +33,13 @@ public class OrderService { private final OrderRepository orderRepository; private final UserRepository userRepository; - private final MenuRepository menuRepository; + private final StoreRepository storeRepository; @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findById(storeId).orElseThrow(MenuNotFoundException::new); + storeRepository.findByStoreIdAndDeletedFalse(storeId) + .orElseThrow(StoreNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new OrderViewUnauthorizedException(); } From e432706d4b75ca885e887fce7ff0d1d2ff832232 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 16:42:50 +0900 Subject: [PATCH 107/685] =?UTF-8?q?feat(User,Token):=20=EB=A6=AC=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 4 -- .../token/controller/TokenController.java | 17 ++++--- .../user/serivce/UserService.java | 48 +++++++++++++++++-- .../oauth2/OAuth2LoginSuccessHandler.java | 40 +++++++++------- .../token/controller/TokenController.java | 26 +++++----- .../domaincorerdb/token/entity/Token.java | 7 ++- .../token/repository/TokenRepository.java | 4 ++ 7 files changed, 96 insertions(+), 50 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index ab54a7c8..19ec26c7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -9,16 +9,12 @@ import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; -import com.nowait.domaincorerdb.menu.entity.Menu; -import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; -import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.order.repository.OrderRepository; -import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java index 5b07ce24..3805508b 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java @@ -3,14 +3,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationadmin.security.jwt.JwtUtil; import com.nowait.applicationadmin.token.dto.AuthenticationResponse; -import com.nowait.applicationadmin.token.dto.RefreshTokenRequest; import com.nowait.applicationadmin.token.service.TokenService; import io.swagger.v3.oas.annotations.Operation; @@ -35,25 +34,25 @@ public class TokenController { @PostMapping @Operation(summary = "리프레시 토큰", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰과 리프레시 토큰을 발급합니다.") @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰과 리프레시 토큰 발급 성공") - public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ - String refreshToken = request.getRefreshToken(); + public ResponseEntity refreshToken( + @CookieValue(value = "refreshToken", required = false) String refreshToken) { + + if (refreshToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh token not found in cookies"); + } // 리프레시 토큰 검증 Long userId = jwtUtil.getUserId(refreshToken); String role = jwtUtil.getRole(refreshToken); - // 리프레시 토큰 유효성 검증 if (tokenService.validateToken(refreshToken, userId)){ - // 유효한 토큰이라면, 새로운 accessToken, refreshToken 생성 String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, accessTokenExpiration); String newRefreshToken = jwtUtil.createRefreshToken("refreshToken", userId, refreshTokenExpiration); - // DB에 새로운 refreshToken으로 교체 tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); - AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, refreshToken); + AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); return ResponseEntity.ok().body(authenticationResponse); - } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java index 669a202b..837d2b8b 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java @@ -1,6 +1,12 @@ package com.nowait.applicationadmin.user.serivce; +import java.time.LocalDateTime; +import java.util.Optional; + import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -13,6 +19,8 @@ import com.nowait.applicationadmin.user.dto.ManagerLoginResponseDto; import com.nowait.applicationadmin.user.dto.ManagerSignupRequestDto; import com.nowait.applicationadmin.user.dto.ManagerSignupResponseDto; +import com.nowait.domaincorerdb.token.entity.Token; +import com.nowait.domaincorerdb.token.repository.TokenRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.repository.UserRepository; @@ -25,6 +33,7 @@ @Slf4j public class UserService { private final UserRepository userRepository; + private final TokenRepository tokenRepository; private final PasswordEncoder passwordEncoder; private final AuthenticationProvider authenticationProvider; private final JwtUtil jwtUtil; @@ -54,19 +63,50 @@ private void validateNickNameDuplicated(String nickName) { ); } @Transactional - public ManagerLoginResponseDto login(ManagerLoginRequestDto managerLoginRequestDto) { + public ResponseEntity login(ManagerLoginRequestDto managerLoginRequestDto) { Authentication authentication = authenticationProvider.authenticate( - new UsernamePasswordAuthenticationToken(managerLoginRequestDto.getEmail(), managerLoginRequestDto.getPassword()) + new UsernamePasswordAuthenticationToken( + managerLoginRequestDto.getEmail(), + managerLoginRequestDto.getPassword() + ) ); MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); User user = userRepository.getReferenceById(memberDetails.getId()); long currentAccessTokenExpiration = accessTokenExpiration; if (user.getRole() == com.nowait.common.enums.Role.SUPER_ADMIN) { - currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일 + currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일 } String accessToken = jwtUtil.createAccessToken("accessToken", user.getId(), String.valueOf(user.getRole()), currentAccessTokenExpiration); - return ManagerLoginResponseDto.fromEntity(user,accessToken); + String refreshToken = jwtUtil.createRefreshToken("refreshToken", user.getId(), 30L * 24 * 60 * 60 * 1000L); + + // 기존 토큰 존재 확인 + Optional tokenOptional = tokenRepository.findByUserId(user.getId()); + if (tokenOptional.isPresent()) { + Token token = tokenOptional.get(); + token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30L)); // 엔티티에 update 메소드 구현 권장 + } else { + tokenRepository.save( + Token.builder() + .user(user) + .refreshToken(refreshToken) + .expiredDate(LocalDateTime.now().plusDays(30L)) + .build() + ); + } + ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken) + .httpOnly(true) + .secure(true) // 운영환경에 맞게 + .path("/") + .maxAge(30L * 24 * 60 * 60) + .sameSite("Strict") + .build(); + + return ResponseEntity.ok() + .header(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()) + .body(ManagerLoginResponseDto.fromEntity(user, accessToken)); + } + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 0417d1c9..f583cc28 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -2,23 +2,20 @@ import java.io.IOException; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; +import java.util.Optional; +import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; -import com.fasterxml.jackson.databind.ObjectMapper; import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.domaincorerdb.token.entity.Token; import com.nowait.domaincorerdb.token.repository.TokenRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -37,6 +34,7 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan private final TokenRepository tokenRepository; @Override + @Transactional public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { @@ -49,19 +47,27 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 30 * 60 * 1000L); // 30분 String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, 30L * 24 * 60 * 60 * 1000L); // 30일 - // 1. refreshToken을 DB에 저장 - Token refreshTokenEntity = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(refreshTokenEntity); + // 1. refreshToken을 DB에 저장 or update + Optional tokenOptional = tokenRepository.findByUserId(user.getId()); + if (tokenOptional.isPresent()) { + Token token = tokenOptional.get(); + token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); + } else { + Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); + tokenRepository.save(token); + } - // 2. refreshToken을 HttpOnly 쿠키로 설정 - Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken); - refreshTokenCookie.setHttpOnly(true); // JS 접근 불가 - refreshTokenCookie.setSecure(false); // 운영환경 https라면 true로 변경 필요 - refreshTokenCookie.setPath("/"); - refreshTokenCookie.setMaxAge(30 * 24 * 60 * 60); // 30일 - response.addCookie(refreshTokenCookie); - response.addHeader("Set-Cookie", response.getHeader("Set-Cookie") + "; SameSite=Lax"); + // 2. refreshToken을 HttpOnly 쿠키로 설정 (ResponseCookie로) + ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken) + .httpOnly(true) + .secure(false) // 운영환경에서는 true + .path("/") + .maxAge(30L * 24 * 60 * 60) // 30일 (초 단위) + .sameSite("Lax") + .build(); + // 기존 방식 대신 ResponseCookie.toString()을 헤더로 추가 + response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) String targetUrl = "http://localhost:5173/login/success?accessToken=" + accessToken; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index f1929331..bc04bc11 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -31,32 +32,27 @@ public class TokenController { private long refreshTokenExpiration; @PostMapping - @Operation(summary = "리프레시 토큰으로 새로운 액세스 토큰 발급", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급합니다.") - @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰 발급 성공") - public ResponseEntity refreshToken(@RequestBody RefreshTokenRequest request){ - String refreshToken = request.getRefreshToken(); + @Operation(summary = "리프레시 토큰", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰과 리프레시 토큰을 발급합니다.") + @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰과 리프레시 토큰 발급 성공") + public ResponseEntity refreshToken( + @CookieValue(value = "refreshToken", required = false) String refreshToken) { + + if (refreshToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh token not found in cookies"); + } // 리프레시 토큰 검증 Long userId = jwtUtil.getUserId(refreshToken); String role = jwtUtil.getRole(refreshToken); - long currentAccessTokenExpiration = accessTokenExpiration; - if (role.equals("SUPER_ADMIN")) { - currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일 - } - - // 리프레시 토큰 유효성 검증 if (tokenService.validateToken(refreshToken, userId)){ - // 유효한 토큰이라면, 새로운 accessToken, refreshToken 생성 - String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, currentAccessTokenExpiration); + String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, accessTokenExpiration); String newRefreshToken = jwtUtil.createRefreshToken("refreshToken", userId, refreshTokenExpiration); - // DB에 새로운 refreshToken으로 교체 tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); - AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, refreshToken); + AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); return ResponseEntity.ok().body(authenticationResponse); - } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java index 14378dd6..29206c5f 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java @@ -28,7 +28,7 @@ public class Token { private Long tokenId; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) + @JoinColumn(name = "user_id", nullable = false,unique = true) private User user; @Column @@ -52,4 +52,9 @@ public static Token toEntity(User user, String refreshToken, LocalDateTime expir .build(); } + public void updateRefreshToken(String refreshToken, LocalDateTime expiredDate) { + this.refreshToken = refreshToken; + this.expiredDate = expiredDate; + } + } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java index ab74bc46..4cc7b9ec 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java @@ -6,8 +6,12 @@ import org.springframework.stereotype.Repository; import com.nowait.domaincorerdb.token.entity.Token; +import com.nowait.domaincorerdb.user.entity.User; @Repository public interface TokenRepository extends JpaRepository { Optional findByUserId(Long userId); + + Optional findByUser(User user); + } From b31fc0139c31920ccd15b2bde7f9a38730fdd93e Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 17:18:58 +0900 Subject: [PATCH 108/685] =?UTF-8?q?feat(User,Token):=20=EB=A6=AC=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationadmin/token/service/TokenService.java | 5 +---- .../applicationuser/token/controller/TokenController.java | 2 -- .../nowait/applicationuser/token/service/TokenService.java | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java index 86364d92..44999a1c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/service/TokenService.java @@ -58,9 +58,6 @@ public void updateRefreshToken(Long userId, String oldRefreshToken, String newRe throw new TokenBadRequestException(); // 400 } - // 기존 토큰 삭제 및 새 토큰 저장 - tokenRepository.delete(token); - Token newToken = Token.toEntity(token.getUser(), newRefreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(newToken); + token.updateRefreshToken(newRefreshToken, LocalDateTime.now().plusDays(30)); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index bc04bc11..fc6a99e2 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -5,13 +5,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.dto.RefreshTokenRequest; import com.nowait.applicationuser.token.service.TokenService; import io.swagger.v3.oas.annotations.Operation; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java index 0b023a15..eee87b82 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java @@ -58,8 +58,6 @@ public void updateRefreshToken(Long userId, String oldRefreshToken, String newRe } // 기존 토큰 삭제 및 새 토큰 저장 - tokenRepository.delete(token); - Token newToken = Token.toEntity(token.getUser(), newRefreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(newToken); + token.updateRefreshToken(newRefreshToken, LocalDateTime.now().plusDays(30)); } } From 0806f71b89393fe63bb1631e61278ade5afdf1b5 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 18:20:44 +0900 Subject: [PATCH 109/685] =?UTF-8?q?feat(Reservation):=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EB=93=B1=EB=A1=9D=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 8 ++++++++ .../oauth/oauth2/CustomOAuth2UserService.java | 1 + .../reservation/service/ReservationService.java | 14 +++++++++++++- .../com/nowait/common/exception/ErrorMessage.java | 1 + .../exception/DuplicateReservationException.java | 10 ++++++++++ .../repository/ReservationRepository.java | 5 +++++ 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/DuplicateReservationException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 116ade92..57f9d0b6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -26,6 +26,7 @@ import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -162,6 +163,13 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(DuplicateReservationException.class) + public ErrorResponse duplicateReservationException(DuplicateReservationException e) { + log.error("duplicateReservationException", e); + return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); + } + private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java index dd5e7855..a7e12685 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java @@ -54,6 +54,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic .profileImage(oAuth2Response.getProfileImage()) .socialType(SocialType.KAKAO) .role(Role.USER) // 일반 유저 설정 + .storeId(0L) .build(); userRepository.save(user); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 2e79bbda..86e69ad8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -1,6 +1,7 @@ package com.nowait.applicationuser.reservation.service; import java.time.LocalDateTime; +import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -11,11 +12,13 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; +import com.sun.jdi.request.DuplicateRequestException; import lombok.RequiredArgsConstructor; @@ -31,10 +34,19 @@ public class ReservationService { public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto) { Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 store")); + .orElseThrow(StoreNotFoundException::new); User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); + // 중복 예약 존재 여부 확인 + boolean hasOngoingReservation = reservationRepository.existsByUserAndStoreAndStatusIn( + user, + store, + List.of(ReservationStatus.WAITING, ReservationStatus.CALLING) + ); + if (hasOngoingReservation) { + throw new DuplicateRequestException(); + } Reservation reservation = Reservation.builder() .store(store) .user(user) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 5a651f45..e521cfdc 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -27,6 +27,7 @@ public enum ErrorMessage { NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"), RESERVATION_VIEW_UNAUTHORIZED("예약 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation002"), RESERVATION_UPDATE_UNAUTHORIZED("예약 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation003"), + DUPLICATE_RESERVATION("이미 대기 중인 예약이 존재합니다.", "reservation004"), // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/DuplicateReservationException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/DuplicateReservationException.java new file mode 100644 index 00000000..588a0a49 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/DuplicateReservationException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class DuplicateReservationException extends RuntimeException { + public DuplicateReservationException() { + super(ErrorMessage.DUPLICATE_RESERVATION.getMessage()); + } +} + diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 874469c8..235242b9 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -5,9 +5,14 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.user.entity.User; @Repository public interface ReservationRepository extends JpaRepository { List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); + boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses); + } From ee8fd1cc243b76e12834ccdd50a8e3dc5d5c609c Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 18:26:21 +0900 Subject: [PATCH 110/685] =?UTF-8?q?feat(Reservation):=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EB=93=B1=EB=A1=9D=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 86e69ad8..4bb0169a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -10,6 +10,7 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; @@ -18,7 +19,6 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; -import com.sun.jdi.request.DuplicateRequestException; import lombok.RequiredArgsConstructor; @@ -45,7 +45,7 @@ public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User custom List.of(ReservationStatus.WAITING, ReservationStatus.CALLING) ); if (hasOngoingReservation) { - throw new DuplicateRequestException(); + throw new DuplicateReservationException(); } Reservation reservation = Reservation.builder() .store(store) From f7b311f5746ae98954efeb3978a2e0f1e9cc8753 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 8 Jul 2025 18:42:05 +0900 Subject: [PATCH 111/685] =?UTF-8?q?feat(Store):=20store=20=EC=9B=A8?= =?UTF-8?q?=EC=9D=B4=ED=8C=85=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=95=BD=20=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예약 create 시 방어로직 추가 --- .../exception/GlobalExceptionHandler.java | 8 ++++++++ .../reservation/service/ReservationService.java | 5 ++++- .../java/com/nowait/common/exception/ErrorMessage.java | 1 + .../store/exception/StoreWaitingDisabledException.java | 9 +++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreWaitingDisabledException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 57f9d0b6..78a405c0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -28,6 +28,7 @@ import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkOwnerMismatchException; @@ -170,6 +171,13 @@ public ErrorResponse duplicateReservationException(DuplicateReservationException return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); } + @ResponseStatus(value = BAD_REQUEST) + @ExceptionHandler(StoreWaitingDisabledException.class) + public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException e) { + log.error("storeWaitingDisabledException", e); + return new ErrorResponse(e.getMessage(), STORE_WAITING_DISABLED.getCode()); + } + private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 4bb0169a..d5dde8ab 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -14,6 +14,7 @@ import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -37,7 +38,9 @@ public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User custom .orElseThrow(StoreNotFoundException::new); User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); - + // store 웨이팅 비활성화 여부 + if (Boolean.FALSE.equals(store.getIsActive())) + throw new StoreWaitingDisabledException(); // 중복 예약 존재 여부 확인 boolean hasOngoingReservation = reservationRepository.existsByUserAndStoreAndStatusIn( user, diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index e521cfdc..02a107a1 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -47,6 +47,7 @@ public enum ErrorMessage { STORE_VIEW_UNAUTHORIZED("주점 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store003"), STORE_UPDATE_UNAUTHORIZED("주점 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store004"), STORE_DELETE_UNAUTHORIZED("주점 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store005"), + STORE_WAITING_DISABLED("해당 주점은 대기 비활성화된 주점입니다.", "store006"), // image IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreWaitingDisabledException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreWaitingDisabledException.java new file mode 100644 index 00000000..cf1d2c49 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreWaitingDisabledException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.store.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StoreWaitingDisabledException extends RuntimeException { + public StoreWaitingDisabledException() { + super(ErrorMessage.STORE_WAITING_DISABLED.getMessage()); + } +} From 28cc199429f5ec5174ed79128520cc6338d33157 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:25:40 +0900 Subject: [PATCH 112/685] =?UTF-8?q?feat:=20=EB=8F=84=EC=BB=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 97 +++++++++++++++++++++++ .github/workflows/deploy-admin.yml | 2 +- docker-compose.yml | 49 ++++++++++++ nowait-app-admin-api/Dockerfile | 8 ++ nowait-app-user-api/Dockerfile | 8 ++ nowait-common/Dockerfile | 6 ++ 6 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/deploy-admin-docker.yml create mode 100644 docker-compose.yml create mode 100644 nowait-app-admin-api/Dockerfile create mode 100644 nowait-app-user-api/Dockerfile create mode 100644 nowait-common/Dockerfile diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml new file mode 100644 index 00000000..b53517cb --- /dev/null +++ b/.github/workflows/deploy-admin-docker.yml @@ -0,0 +1,97 @@ +name: Dev Admin CI/CD With Docker + +on: + push: + branches: + - develop + paths: + - 'nowait-app-admin-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-admin-rdb/**' + - 'nowait-infra/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' + +env: + DOCKERHUB_REPO: jihuneddie/nowait_dev + DIVISION: admin + IMAGE_TAG: ${{ env.DIVISION }}-${{ github.sha }} + EC2_DEPLOY_PATH: /home/ubuntu/spring-github-action-admin-docker + + +jobs: + build-with-gradle: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Set YML + run: | + mkdir -p nowait-app-admin-api/src/main/resources + echo "${{ secrets.APPLICATION_DEV_ADMIN_YML }}" \ + | base64 --decode \ + > nowait-app-admin-api/src/main/resources/application-admin.yml + find nowait-app-admin-api/src + + - name: Build Admin JAR + run: ./gradlew clean :nowait-app-admin-api:bootJar -x test + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + + - name: Build and Push Docker Image + run: | + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/admin/nowait-app-admin-api ./nowait-app-admin-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/admin/nowait-app-admin-api + + + deploy: + name: Deploy to EC2 + runs-on: ubuntu-latest + needs: build-with-gradle + + steps: + - uses: actions/checkout@v4 + - name: Copy Docker Compose File + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.EC2_HOST }} + username: ubuntu + key: ${{ secrets.EC2_KEY }} + source: "docker-compose.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Deploy to EC2 + uses: appleboy/ssh-action@v1.0.3 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ap-northeast-2 + DOCKER_HUB_NAMESPACE: ${{ secrets.DOCKER_HUB_NAMESPACE }} + with: + host: ${{ secrets.EC2_HOST }} + username: ubuntu + key: ${{ secrets.EC2_KEY }} + port: 22 + envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, DOCKER_HUB_NAMESPACE + script: | + cd ${{ env.EC2_DEPLOY_PATH }} + sudo docker-compose pull nowait-app-admin-api + sudo docker-compose up -d nowait-app-admin-api + diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 5c9889c2..5481860f 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -18,7 +18,7 @@ env: PROJECT_NAME: NoWait DIVISION: admin AWS_REGION: ap-northeast-2 - AWS_S3_BUCKET: nowait-deploy-github-actions + AWS_S3_BUCKET: nowait-deploy-github- actions AWS_CODE_DEPLOY_APPLICATION: nowaiting AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-admin diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..853ac661 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +name: nowait_dev + +services: + mysql: + image: mysql:8.0 + container_name: mysql_nowait_container + environment: + MYSQL_ROOT_PASSWORD: gtable1118! + MYSQL_DATABASE: gtable_dev + MYSQL_USER: developer + MYSQL_PASSWORD: gtable1118! + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + + nowait-app-admin-api: +# build: +# context: nowait-app-admin-api +# dockerfile: Dockerfile + image: jihuneddie/admin/nowait-app-admin-api:latest + container_name: + nowait-app-admin-api + ports: + - "8085:8086" + depends_on: + - mysql + volumes: + - ./nowait-app-admin-api:/app + restart: + always + + nowait-app-user-api: + build: + context: nowait-app-user-api + dockerfile: Dockerfile + container_name: + nowait-app-user-api + ports: + - "8081:8081" + depends_on: + - mysql + volumes: + - ./nowait-app-user-api:/app + restart: + always + +volumes: + mysql_data: diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile new file mode 100644 index 00000000..fd4cd318 --- /dev/null +++ b/nowait-app-admin-api/Dockerfile @@ -0,0 +1,8 @@ +FROM openjdk:17 +ARG JAR_FILE=./build/libs/*.jar +COPY ${JAR_FILE} nowait-app-admin-api.jar + +ARG PROFILE=admin +ENV PROFILE=${PROFILE} + +CMD ["java", "-jar", "nowait-app-admin-api.jar", "--spring.profiles.active=${PROFILE}", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile new file mode 100644 index 00000000..32684f04 --- /dev/null +++ b/nowait-app-user-api/Dockerfile @@ -0,0 +1,8 @@ +FROM openjdk:17 +ARG JAR_FILE=./build/libs/*.jar +COPY ${JAR_FILE} nowait-app-user-api.jar + +ARG PROFILE=user +ENV PROFILE=${PROFILE} + +CMD ["java", "-jar", "nowait-app-user-api.jar", "--spring.profiles.active=${PROFILE}", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] diff --git a/nowait-common/Dockerfile b/nowait-common/Dockerfile new file mode 100644 index 00000000..9c4d8549 --- /dev/null +++ b/nowait-common/Dockerfile @@ -0,0 +1,6 @@ +FROM openjdk:17 + +WORKDIR /app +COPY build/libs/*.jar nowait-common.jar + +CMD ["java", "-jar", "nowait-common.jar"] From 2b1241e5a90c2882902146e4be82ec6f67ec9e2c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:44:33 +0900 Subject: [PATCH 113/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index b53517cb..842c2aa3 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -80,16 +80,14 @@ jobs: - name: Deploy to EC2 uses: appleboy/ssh-action@v1.0.3 env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} AWS_REGION: ap-northeast-2 - DOCKER_HUB_NAMESPACE: ${{ secrets.DOCKER_HUB_NAMESPACE }} with: host: ${{ secrets.EC2_HOST }} - username: ubuntu - key: ${{ secrets.EC2_KEY }} - port: 22 - envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, DOCKER_HUB_NAMESPACE + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} sudo docker-compose pull nowait-app-admin-api From 6d8308ef6b75dedf61221e4df6b9257257bb4760 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:47:36 +0900 Subject: [PATCH 114/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 842c2aa3..49c52994 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -17,7 +17,6 @@ on: env: DOCKERHUB_REPO: jihuneddie/nowait_dev DIVISION: admin - IMAGE_TAG: ${{ env.DIVISION }}-${{ github.sha }} EC2_DEPLOY_PATH: /home/ubuntu/spring-github-action-admin-docker From 8419601a3c37e69a9a582e7e9d2129b39b3ed385 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:50:40 +0900 Subject: [PATCH 115/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin.yml | 2 +- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 5481860f..5c9889c2 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -18,7 +18,7 @@ env: PROJECT_NAME: NoWait DIVISION: admin AWS_REGION: ap-northeast-2 - AWS_S3_BUCKET: nowait-deploy-github- actions + AWS_S3_BUCKET: nowait-deploy-github-actions AWS_CODE_DEPLOY_APPLICATION: nowaiting AWS_CODE_DEPLOY_GROUP: nowaiting-deploy-dev-admin diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From b98f6c3fa66dbd9c415330ec03147d38e07a0c6b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:55:28 +0900 Subject: [PATCH 116/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 9 +++++---- docker-compose.yml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 49c52994..468bca8e 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -56,11 +56,12 @@ jobs: - name: Build and Push Docker Image run: | - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/admin/nowait-app-admin-api ./nowait-app-admin-api - docker push ${{ secrets.DOCKERHUB_USERNAME }}/admin/nowait-app-admin-api - + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin-${{ github.sha }} ./nowait-app-admin-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin-${{ github.sha }} - deploy: + + +deploy: name: Deploy to EC2 runs-on: ubuntu-latest needs: build-with-gradle diff --git a/docker-compose.yml b/docker-compose.yml index 853ac661..60c052da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: # build: # context: nowait-app-admin-api # dockerfile: Dockerfile - image: jihuneddie/admin/nowait-app-admin-api:latest + image: jihuneddie/nowait_dev:admin- container_name: nowait-app-admin-api ports: From 329991d2bf5f1377f50326dd4d844786a9e67642 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:55:45 +0900 Subject: [PATCH 117/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From ca5a992a09d7db1812be4e1e17b4aa76a12399ae Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:57:21 +0900 Subject: [PATCH 118/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 64 +++++++++++------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 468bca8e..26a8a7d2 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -61,35 +61,35 @@ jobs: -deploy: - name: Deploy to EC2 - runs-on: ubuntu-latest - needs: build-with-gradle - - steps: - - uses: actions/checkout@v4 - - name: Copy Docker Compose File - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.EC2_HOST }} - username: ubuntu - key: ${{ secrets.EC2_KEY }} - source: "docker-compose.yml" - target: ${{ env.EC2_DEPLOY_PATH }} - - - name: Deploy to EC2 - uses: appleboy/ssh-action@v1.0.3 - env: - AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - AWS_REGION: ap-northeast-2 - with: - host: ${{ secrets.EC2_HOST }} - username: ${{ secrets.EC2_USER }} - key: ${{ secrets.EC2_SSH_KEY }} - envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION - script: | - cd ${{ env.EC2_DEPLOY_PATH }} - sudo docker-compose pull nowait-app-admin-api - sudo docker-compose up -d nowait-app-admin-api - + deploy: + name: Deploy to EC2 + runs-on: ubuntu-latest + needs: build-with-gradle + + steps: + - uses: actions/checkout@v4 + - name: Copy Docker Compose File + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.EC2_HOST }} + username: ubuntu + key: ${{ secrets.EC2_KEY }} + source: "docker-compose.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Deploy to EC2 + uses: appleboy/ssh-action@v1.0.3 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} + AWS_REGION: ap-northeast-2 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + script: | + cd ${{ env.EC2_DEPLOY_PATH }} + sudo docker-compose pull nowait-app-admin-api + sudo docker-compose up -d nowait-app-admin-api + From 4be9ad332001f68d881bf7a2ce8d524a16ebef4c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 19:58:24 +0900 Subject: [PATCH 119/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 3498e1986e9b377e88f7939b5a32f7d8a1dd3250 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 20:14:30 +0900 Subject: [PATCH 120/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 26a8a7d2..a2290fdd 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -73,7 +73,7 @@ jobs: with: host: ${{ secrets.EC2_HOST }} username: ubuntu - key: ${{ secrets.EC2_KEY }} + key: ${{ secrets.EC2_SSH_KEY }} source: "docker-compose.yml" target: ${{ env.EC2_DEPLOY_PATH }} From bf7372078b24b97d1d745776282ff873b9a380c2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 20:14:50 +0900 Subject: [PATCH 121/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 9fdd8726df7f84f4678c4259807d8176cdfe7aa4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 20:36:02 +0900 Subject: [PATCH 122/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index a2290fdd..6e56eef3 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -69,7 +69,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Copy Docker Compose File - uses: appleboy/scp-action@v0.1.7 + uses: appleboy/ssh-action@master with: host: ${{ secrets.EC2_HOST }} username: ubuntu From c1329887df24c2c7890b3b4913f58e9b05627a7f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 20:36:21 +0900 Subject: [PATCH 123/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From b3f1db33417b3f8ff596c94d49797e33f2502dbf Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 20:46:34 +0900 Subject: [PATCH 124/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 6e56eef3..31a17932 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -67,18 +67,8 @@ jobs: needs: build-with-gradle steps: - - uses: actions/checkout@v4 - - name: Copy Docker Compose File - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.EC2_HOST }} - username: ubuntu - key: ${{ secrets.EC2_SSH_KEY }} - source: "docker-compose.yml" - target: ${{ env.EC2_DEPLOY_PATH }} - - name: Deploy to EC2 - uses: appleboy/ssh-action@v1.0.3 + uses: appleboy/ssh-action@master env: AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} From 571b267a64e0e759d1fd3a11ac51fdeae9fc4328 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 20:47:01 +0900 Subject: [PATCH 125/685] =?UTF-8?q?refactor:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 2 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 07db6d34..8e25d58f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,3 +11,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From d2f83a9b5bb14da70aa94f4b39ce214038c52d23 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 21:00:37 +0900 Subject: [PATCH 126/685] =?UTF-8?q?refactor:=20=ED=82=A4=20=EA=B0=92=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 4 ++++ .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 31a17932..a03ab044 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -67,6 +67,10 @@ jobs: needs: build-with-gradle steps: + - name: Debug SSH key length + run: | + echo "Length: $(echo "${{ secrets.EC2_SSH_KEY }}" | wc -c)" + - name: Deploy to EC2 uses: appleboy/ssh-action@master env: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 121a91663d5482583de69a6d893b8182be111101 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 21:06:03 +0900 Subject: [PATCH 127/685] =?UTF-8?q?refactor:=20=ED=82=A4=20=EA=B0=92=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index a03ab044..42d07f44 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -72,7 +72,7 @@ jobs: echo "Length: $(echo "${{ secrets.EC2_SSH_KEY }}" | wc -c)" - name: Deploy to EC2 - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v0.1.4 env: AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 6ba7aa8de6415fd57c6172d27eda901476180e12 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 21:11:52 +0900 Subject: [PATCH 128/685] =?UTF-8?q?refactor:=20=ED=82=A4=20=EA=B0=92=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 42d07f44..d7341cfc 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -67,10 +67,6 @@ jobs: needs: build-with-gradle steps: - - name: Debug SSH key length - run: | - echo "Length: $(echo "${{ secrets.EC2_SSH_KEY }}" | wc -c)" - - name: Deploy to EC2 uses: appleboy/ssh-action@v0.1.4 env: @@ -81,6 +77,7 @@ jobs: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} From a476c820a63be99413e54794db24f4233e2d1783 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 21:11:59 +0900 Subject: [PATCH 129/685] =?UTF-8?q?refactor:=20=ED=82=A4=20=EA=B0=92=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 235dc55f69b6dfbad51c51559da450e367767a86 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 21:32:49 +0900 Subject: [PATCH 130/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 52 +++++++++++-------- .../java/com/nowait/ApiAdminApplication.java | 1 + 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index d7341cfc..7b5fdadf 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -62,25 +62,35 @@ jobs: deploy: - name: Deploy to EC2 - runs-on: ubuntu-latest - needs: build-with-gradle - - steps: - - name: Deploy to EC2 - uses: appleboy/ssh-action@v0.1.4 - env: - AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} - AWS_REGION: ap-northeast-2 - with: - host: ${{ secrets.EC2_HOST }} - username: ${{ secrets.EC2_USER }} - key: ${{ secrets.EC2_SSH_KEY }} - port: ${{ secrets.EC2_PORT }} - envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION - script: | - cd ${{ env.EC2_DEPLOY_PATH }} - sudo docker-compose pull nowait-app-admin-api - sudo docker-compose up -d nowait-app-admin-api + name: Deploy to EC2 + runs-on: ubuntu-latest + needs: build-with-gradle + + steps: + - name: Copy docker-compose.yml to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "docker-compose.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Deploy to EC2 + uses: appleboy/ssh-action@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} + AWS_REGION: ap-northeast-2 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + script: | + cd ${{ env.EC2_DEPLOY_PATH }} + sudo docker-compose pull nowait-app-admin-api + sudo docker-compose up -d nowait-app-admin-api diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 458476047ff2bbba980e02fa2e5589d1aa7b1dea Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 21:44:26 +0900 Subject: [PATCH 131/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 3 +++ .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 7b5fdadf..ecee918e 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -67,6 +67,9 @@ jobs: needs: build-with-gradle steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Copy docker-compose.yml to EC2 uses: appleboy/scp-action@master with: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 90865e3f48748eff85c452135ba10ac7a14bf637 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 22:07:27 +0900 Subject: [PATCH 132/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=83=9C=EA=B7=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 4 +-- docker-compose.yml | 34 ++++++++++------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index ecee918e..03218c73 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -56,8 +56,8 @@ jobs: - name: Build and Push Docker Image run: | - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin-${{ github.sha }} ./nowait-app-admin-api - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin-${{ github.sha }} + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin ./nowait-app-admin-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin diff --git a/docker-compose.yml b/docker-compose.yml index 60c052da..b0e5c509 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,32 +18,28 @@ services: # build: # context: nowait-app-admin-api # dockerfile: Dockerfile - image: jihuneddie/nowait_dev:admin- - container_name: - nowait-app-admin-api + image: jihuneddie/nowait_dev:admin + container_name: nowait-app-admin-api ports: - "8085:8086" depends_on: - mysql volumes: - ./nowait-app-admin-api:/app - restart: - always + restart: always - nowait-app-user-api: - build: - context: nowait-app-user-api - dockerfile: Dockerfile - container_name: - nowait-app-user-api - ports: - - "8081:8081" - depends_on: - - mysql - volumes: - - ./nowait-app-user-api:/app - restart: - always +# nowait-app-user-api: +# build: +# context: nowait-app-user-api +# dockerfile: Dockerfile +# container_name: nowait-app-user-api +# ports: +# - "8081:8081" +# depends_on: +# - mysql +# volumes: +# - ./nowait-app-user-api:/app +# restart: always volumes: mysql_data: From c5a456c061cedb57b37ff71b7ba68828d632b611 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 22:07:39 +0900 Subject: [PATCH 133/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=83=9C=EA=B7=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 44b50cf518d30db028b1a0c58358118883569a4c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 22:25:25 +0900 Subject: [PATCH 134/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EC=8B=A4=ED=96=89=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 4 ++-- docker-compose.yml | 4 +--- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 03218c73..ecb05971 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -94,6 +94,6 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} - sudo docker-compose pull nowait-app-admin-api - sudo docker-compose up -d nowait-app-admin-api + sudo docker-compose -p nowait_dev pull nowait-app-admin-api + sudo docker-compose -p nowait_dev up -d nowait-app-admin-api diff --git a/docker-compose.yml b/docker-compose.yml index b0e5c509..7833a531 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -name: nowait_dev - services: mysql: image: mysql:8.0 @@ -21,7 +19,7 @@ services: image: jihuneddie/nowait_dev:admin container_name: nowait-app-admin-api ports: - - "8085:8086" + - "8085:8085" depends_on: - mysql volumes: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 029583b45a7d32f6b40c436e3e0e9844579fe50e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 22:35:59 +0900 Subject: [PATCH 135/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EC=8B=A4=ED=96=89=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 6 +++++ docker-compose.yml | 26 +++++++++---------- .../java/com/nowait/ApiAdminApplication.java | 1 + 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index ecb05971..b5c0b185 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -80,6 +80,12 @@ jobs: source: "docker-compose.yml" target: ${{ env.EC2_DEPLOY_PATH }} + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + - name: Deploy to EC2 uses: appleboy/ssh-action@master env: diff --git a/docker-compose.yml b/docker-compose.yml index 7833a531..f8fcc56d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,25 +19,25 @@ services: image: jihuneddie/nowait_dev:admin container_name: nowait-app-admin-api ports: - - "8085:8085" + - "8086:8085" depends_on: - mysql volumes: - ./nowait-app-admin-api:/app restart: always -# nowait-app-user-api: -# build: -# context: nowait-app-user-api -# dockerfile: Dockerfile -# container_name: nowait-app-user-api -# ports: -# - "8081:8081" -# depends_on: -# - mysql -# volumes: -# - ./nowait-app-user-api:/app -# restart: always + nowait-app-user-api: + build: + context: nowait-app-user-api + dockerfile: Dockerfile + container_name: nowait-app-user-api + ports: + - "8081:8081" + depends_on: + - mysql + volumes: + - ./nowait-app-user-api:/app + restart: always volumes: mysql_data: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 03f46387d378ed8332e99673a71e793560c43ed1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 22:44:00 +0900 Subject: [PATCH 136/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A6=88=20nowait-app-user-api=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f8fcc56d..aee4f427 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,18 +26,18 @@ services: - ./nowait-app-admin-api:/app restart: always - nowait-app-user-api: - build: - context: nowait-app-user-api - dockerfile: Dockerfile - container_name: nowait-app-user-api - ports: - - "8081:8081" - depends_on: - - mysql - volumes: - - ./nowait-app-user-api:/app - restart: always +# nowait-app-user-api: +# build: +# context: nowait-app-user-api +# dockerfile: Dockerfile +# container_name: nowait-app-user-api +# ports: +# - "8081:8081" +# depends_on: +# - mysql +# volumes: +# - ./nowait-app-user-api:/app +# restart: always volumes: mysql_data: From 3405acead0f0be438f926846c20857d61adc3eda Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 22:46:38 +0900 Subject: [PATCH 137/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A6=88=20nowait-app-user-api=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From e1dc7bb258e4f95598ced4f513bc7a5df1fa1dec Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 8 Jul 2025 23:07:55 +0900 Subject: [PATCH 138/685] =?UTF-8?q?refactor:=20nowait-app-user-api=20?= =?UTF-8?q?=EB=8F=84=EC=BB=A4=20=EB=B0=B0=ED=8F=AC=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 102 ++++++++++++++++++ docker-compose.yml | 19 ++-- .../java/com/nowait/ApiAdminApplication.java | 1 + .../java/com/nowait/ApiUserApplication.java | 1 - 4 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/deploy-user-docker.yml diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml new file mode 100644 index 00000000..dec033a4 --- /dev/null +++ b/.github/workflows/deploy-user-docker.yml @@ -0,0 +1,102 @@ +name: Dev User CI/CD With Docker + +on: + push: + branches: + - develop + paths: + - 'nowait-app-user-api/**' + - 'nowait-common/**' + - 'nowait-domain/domain-core-rdb/**' + - 'nowait-domain/domain-user-rdb/**' + - 'nowait-infra/**' + - 'build.gradle' + - 'settings.gradle' + - 'gradle/**' + +env: + DOCKERHUB_REPO: jihuneddie/nowait_dev + DIVISION: user + EC2_DEPLOY_PATH: /home/ubuntu/spring-github-action-user-docker + +jobs: + build-with-gradle: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Set YML + run: | + mkdir -p nowait-app-user-api/src/main/resources + echo "${{ secrets.APPLICATION_DEV_USER_YML }}" \ + | base64 --decode \ + > nowait-app-user-api/src/main/resources/application-user.yml + find nowait-app-user-api/src + + - name: Build User JAR + run: ./gradlew clean :nowait-app-user-api:bootJar -x test + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + + - name: Build and Push Docker Image + run: | + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:user ./nowait-app-user-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:user + + deploy: + name: Deploy to EC2 + runs-on: ubuntu-latest + needs: build-with-gradle + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Copy docker-compose.yml to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "docker-compose.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + - name: Deploy to EC2 + uses: appleboy/ssh-action@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DEVSERVER_CICD_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEVSERVER_CICD_SECRET_KEY }} + AWS_REGION: ap-northeast-2 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + script: | + cd ${{ env.EC2_DEPLOY_PATH }} + sudo docker-compose -p nowait_dev pull nowait-app-user-api + sudo docker-compose -p nowait_dev up -d nowait-app-user-api + diff --git a/docker-compose.yml b/docker-compose.yml index aee4f427..32947253 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,18 +26,19 @@ services: - ./nowait-app-admin-api:/app restart: always -# nowait-app-user-api: + nowait-app-user-api: # build: # context: nowait-app-user-api # dockerfile: Dockerfile -# container_name: nowait-app-user-api -# ports: -# - "8081:8081" -# depends_on: -# - mysql -# volumes: -# - ./nowait-app-user-api:/app -# restart: always + image: jihuneddie/nowait_dev:user + container_name: nowait-app-user-api + ports: + - "8082:8081" + depends_on: + - mysql + volumes: + - ./nowait-app-user-api:/app + restart: always volumes: mysql_data: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 8e25d58f..07db6d34 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,4 +11,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From 32bdfe53e8668f56f9c6a49f8a5987bece452631 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 00:05:58 +0900 Subject: [PATCH 139/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 1 + .github/workflows/deploy-user-docker.yml | 1 + nowait-app-admin-api/Dockerfile | 3 +-- nowait-app-user-api/Dockerfile | 3 +-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index b5c0b185..0d6dfbc6 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -100,6 +100,7 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} + sudo docker rm -f nowait-app-admin-api || true sudo docker-compose -p nowait_dev pull nowait-app-admin-api sudo docker-compose -p nowait_dev up -d nowait-app-admin-api diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index dec033a4..b059db61 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -97,6 +97,7 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} + sudo docker rm -f nowait-app-user-api || true sudo docker-compose -p nowait_dev pull nowait-app-user-api sudo docker-compose -p nowait_dev up -d nowait-app-user-api diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index fd4cd318..8b17c261 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -3,6 +3,5 @@ ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-admin-api.jar ARG PROFILE=admin -ENV PROFILE=${PROFILE} -CMD ["java", "-jar", "nowait-app-admin-api.jar", "--spring.profiles.active=${PROFILE}", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] +CMD ["java", "-jar", "nowait-app-admin-api.jar", "--spring.profiles.active=admin", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index 32684f04..8daac893 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -3,6 +3,5 @@ ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-user-api.jar ARG PROFILE=user -ENV PROFILE=${PROFILE} -CMD ["java", "-jar", "nowait-app-user-api.jar", "--spring.profiles.active=${PROFILE}", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] +CMD ["java", "-jar", "nowait-app-user-api.jar", "--spring.profiles.active=user", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] From 0b340167f2a75cc984e205c0589c4482d14698c5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 00:06:07 +0900 Subject: [PATCH 140/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 07db6d34..8e25d58f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,3 +11,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From aa4e1560dda4b864f30c1e9482c4d0ac443233ca Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:03:19 +0900 Subject: [PATCH 141/685] =?UTF-8?q?feat:=20=EB=8F=84=EC=BB=A4=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=99=EC=9D=84=20=EC=9C=84=ED=95=9C=20sh=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 15 ++++++++--- .../java/com/nowait/ApiAdminApplication.java | 1 + scripts/deploy-admin-docker | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 scripts/deploy-admin-docker diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 0d6dfbc6..0be43eea 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -80,6 +80,16 @@ jobs: source: "docker-compose.yml" target: ${{ env.EC2_DEPLOY_PATH }} + - name: Copy deploy-admin-docker to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "scripts/deploy-admin-docker" + target: "${{ env.EC2_DEPLOY_PATH }}/scripts/" + - name: Login to DockerHub uses: docker/login-action@v1 with: @@ -100,7 +110,6 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} - sudo docker rm -f nowait-app-admin-api || true - sudo docker-compose -p nowait_dev pull nowait-app-admin-api - sudo docker-compose -p nowait_dev up -d nowait-app-admin-api + chmod +x ./scripts/deploy-admin-docker + ./scripts/deploy-admin-docker diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/scripts/deploy-admin-docker b/scripts/deploy-admin-docker new file mode 100644 index 00000000..b0740906 --- /dev/null +++ b/scripts/deploy-admin-docker @@ -0,0 +1,27 @@ +#!/bin/bash + +REPOSITORY="/home/ubuntu/spring-github-action-admin-docker" +cd $REPOSITORY + +START_LOG="$REPOSITORY/start.log" + +echo "1. find container id" +CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") + +echo "2. stop container" +if [ -n "$CONTAINER_ID" ]; then + echo "Stopping container $CONTAINER_ID" + docker rm -f "$CONTAINER_ID" +else + echo "No admin container found." +fi + +echo "3. start container" +sudo docker-compose -p nowait_dev pull nowait-app-admin-api +sudo docker-compose -p nowait_dev up -d nowait-app-admin-api + +echo "4. check container status" +NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") +NOW=$(date +%c) + +echo "[$NOW] > Container ID: $NEW_CONTAINER_ID" From 2a55070e9f28bec5a038a62890b3c00ccac79c12 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:07:47 +0900 Subject: [PATCH 142/685] =?UTF-8?q?refactor:=20deploy-admin-docker.yml=20?= =?UTF-8?q?=EC=89=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=B5=EC=82=AC=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 0be43eea..3dd073bf 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -88,7 +88,7 @@ jobs: key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} source: "scripts/deploy-admin-docker" - target: "${{ env.EC2_DEPLOY_PATH }}/scripts/" + target: "${{ env.EC2_DEPLOY_PATH }}/" - name: Login to DockerHub uses: docker/login-action@v1 From 16f957fec22ed3b944912291bec8a1fa05c1c7cf Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:09:30 +0900 Subject: [PATCH 143/685] =?UTF-8?q?refactor:=20deploy-admin-docker.yml=20?= =?UTF-8?q?=EC=89=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=B5=EC=82=AC=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 3dd073bf..e2fa7371 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -88,7 +88,7 @@ jobs: key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} source: "scripts/deploy-admin-docker" - target: "${{ env.EC2_DEPLOY_PATH }}/" + target: "${{ env.EC2_DEPLOY_PATH }}" - name: Login to DockerHub uses: docker/login-action@v1 From 248b8b47b1b563dafcdcc421e0f3fb3a075400bf Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:09:47 +0900 Subject: [PATCH 144/685] =?UTF-8?q?test:=20=EB=8F=84=EC=BB=A4=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From 0027e07b4ec309a3186fa30f6f048d7d901d129a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:15:53 +0900 Subject: [PATCH 145/685] =?UTF-8?q?refactor:=20deploy-admin-docker.yml=20?= =?UTF-8?q?=EC=89=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=B5=EC=82=AC=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index e2fa7371..d2f6eefd 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -88,7 +88,7 @@ jobs: key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} source: "scripts/deploy-admin-docker" - target: "${{ env.EC2_DEPLOY_PATH }}" + target: "${{ env.EC2_DEPLOY_PATH }}/scripts" - name: Login to DockerHub uses: docker/login-action@v1 From d80d0eb58e25b13b83d257adf6c2a3baa00c062b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:19:14 +0900 Subject: [PATCH 146/685] =?UTF-8?q?refactor:=20deploy-admin-docker.yml=20?= =?UTF-8?q?=EC=89=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=B5=EC=82=AC=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 2 +- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index d2f6eefd..e2fa7371 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -88,7 +88,7 @@ jobs: key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} source: "scripts/deploy-admin-docker" - target: "${{ env.EC2_DEPLOY_PATH }}/scripts" + target: "${{ env.EC2_DEPLOY_PATH }}" - name: Login to DockerHub uses: docker/login-action@v1 diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From a2647d3f836dfbeb2399cd2ec1d16042834843ad Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:27:52 +0900 Subject: [PATCH 147/685] =?UTF-8?q?refactor:=20deploy-user-docker.yml=20?= =?UTF-8?q?=EC=89=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 15 ++++++++--- .../java/com/nowait/ApiAdminApplication.java | 1 - .../java/com/nowait/ApiUserApplication.java | 1 - scripts/deploy-user-docker.sh | 27 +++++++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 scripts/deploy-user-docker.sh diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index b059db61..08729726 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -77,6 +77,16 @@ jobs: source: "docker-compose.yml" target: ${{ env.EC2_DEPLOY_PATH }} + - name: Copy deploy-user-docker to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "scripts/deploy-user-docker" + target: "${{ env.EC2_DEPLOY_PATH }}" + - name: Login to DockerHub uses: docker/login-action@v1 with: @@ -97,7 +107,6 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} - sudo docker rm -f nowait-app-user-api || true - sudo docker-compose -p nowait_dev pull nowait-app-user-api - sudo docker-compose -p nowait_dev up -d nowait-app-user-api + chmod +x ./scripts/deploy-user-docker + ./scripts/deploy-user-docker diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 8e25d58f..07db6d34 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,4 +11,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh new file mode 100644 index 00000000..0b8d7d9a --- /dev/null +++ b/scripts/deploy-user-docker.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +REPOSITORY="/home/ubuntu/spring-github-action-user-docker" +cd $REPOSITORY + +START_LOG="$REPOSITORY/start.log" + +echo "1. find container id" +CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") + +echo "2. stop container" +if [ -n "$CONTAINER_ID" ]; then + echo "Stopping container $CONTAINER_ID" + docker rm -f "$CONTAINER_ID" +else + echo "No user container found." +fi + +echo "3. start container" +sudo docker-compose -p nowait_dev pull nowait-app-user-api +sudo docker-compose -p nowait_dev up -d nowait-app-user-api + +echo "4. check container status" +NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") +NOW=$(date +%c) + +echo "[$NOW] > Container ID: $NEW_CONTAINER_ID" From b789d1e3b8ca1dbe94d6e7cfc07076a132ff259d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:33:06 +0900 Subject: [PATCH 148/685] =?UTF-8?q?refactor:=20deploy-user-docker.yml=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 4 ++-- .github/workflows/deploy-user-docker.yml | 2 +- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 + scripts/{deploy-admin-docker => deploy-admin-docker.sh} | 0 5 files changed, 5 insertions(+), 3 deletions(-) rename scripts/{deploy-admin-docker => deploy-admin-docker.sh} (100%) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index e2fa7371..bc0caeff 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -80,14 +80,14 @@ jobs: source: "docker-compose.yml" target: ${{ env.EC2_DEPLOY_PATH }} - - name: Copy deploy-admin-docker to EC2 + - name: Copy deploy-admin-docker.sh to EC2 uses: appleboy/scp-action@master with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "scripts/deploy-admin-docker" + source: "scripts/deploy-admin-docker.sh" target: "${{ env.EC2_DEPLOY_PATH }}" - name: Login to DockerHub diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 08729726..53b168ce 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -84,7 +84,7 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "scripts/deploy-user-docker" + source: "scripts/deploy-user-docker.sh" target: "${{ env.EC2_DEPLOY_PATH }}" - name: Login to DockerHub diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 07db6d34..8e25d58f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,3 +11,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + diff --git a/scripts/deploy-admin-docker b/scripts/deploy-admin-docker.sh similarity index 100% rename from scripts/deploy-admin-docker rename to scripts/deploy-admin-docker.sh From 66b4c5a7949a3f9f4115d459e83a979fca096490 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:38:28 +0900 Subject: [PATCH 149/685] =?UTF-8?q?refactor:=20deploy-user-docker.yml=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 4 ++-- .github/workflows/deploy-user-docker.yml | 4 ++-- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index bc0caeff..96786003 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -110,6 +110,6 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} - chmod +x ./scripts/deploy-admin-docker - ./scripts/deploy-admin-docker + chmod +x ./scripts/deploy-admin-docker.sh + ./scripts/deploy-admin-docker.sh diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 53b168ce..c5ad5be8 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -107,6 +107,6 @@ jobs: envs: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION script: | cd ${{ env.EC2_DEPLOY_PATH }} - chmod +x ./scripts/deploy-user-docker - ./scripts/deploy-user-docker + chmod +x ./scripts/deploy-user-docker.sh + ./scripts/deploy-user-docker.sh diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 8e25d58f..07db6d34 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,4 +11,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From f20514314f270c58c5a74220c5b6b73281fc9f4e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 12:55:26 +0900 Subject: [PATCH 150/685] =?UTF-8?q?refactor:=20docker=20compose=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=99=98=EA=B2=BD=EB=B3=84=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/needdelete.md | 1 - ...4\353\223\234-\354\235\264\354\212\210.md" | 14 ++++++ .github/workflows/deploy-admin-docker.yml | 4 +- .github/workflows/deploy-admin.yml | 2 +- .github/workflows/deploy-user-docker.yml | 4 +- .github/workflows/deploy-user.yml | 2 +- docker-compose.yml | 44 ------------------- .../java/com/nowait/ApiAdminApplication.java | 1 + .../java/com/nowait/ApiUserApplication.java | 1 + scripts/deploy-admin-docker.sh | 4 +- scripts/deploy-user-docker.sh | 4 +- 11 files changed, 26 insertions(+), 55 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/needdelete.md create mode 100644 ".github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" delete mode 100644 docker-compose.yml diff --git a/.github/ISSUE_TEMPLATE/needdelete.md b/.github/ISSUE_TEMPLATE/needdelete.md deleted file mode 100644 index 8b137891..00000000 --- a/.github/ISSUE_TEMPLATE/needdelete.md +++ /dev/null @@ -1 +0,0 @@ - diff --git "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" new file mode 100644 index 00000000..f728915f --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" @@ -0,0 +1,14 @@ +--- +name: 백엔드 이슈 +about: 백엔드와 관련된 이슈 +title: "[백엔드] " +labels: Backend +assignees: '' + +--- + +# 이슈 내용 + + +# 작업 목록 +- [ ] diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 96786003..7eea527b 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -70,14 +70,14 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Copy docker-compose.yml to EC2 + - name: Copy docker-compose.admin.yml to EC2 uses: appleboy/scp-action@master with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "docker-compose.yml" + source: "docker-compose.admin.yml" target: ${{ env.EC2_DEPLOY_PATH }} - name: Copy deploy-admin-docker.sh to EC2 diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml index 5c9889c2..e29c7a65 100644 --- a/.github/workflows/deploy-admin.yml +++ b/.github/workflows/deploy-admin.yml @@ -3,7 +3,7 @@ name: Dev Admin CI/CD on: push: branches: - - develop + - legacy paths: - 'nowait-app-admin-api/**' - 'nowait-common/**' diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index c5ad5be8..3836cb6b 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -67,14 +67,14 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Copy docker-compose.yml to EC2 + - name: Copy docker-compose.user.yml to EC2 uses: appleboy/scp-action@master with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "docker-compose.yml" + source: "docker-compose.user.yml" target: ${{ env.EC2_DEPLOY_PATH }} - name: Copy deploy-user-docker to EC2 diff --git a/.github/workflows/deploy-user.yml b/.github/workflows/deploy-user.yml index 0ec7183e..bf98972d 100644 --- a/.github/workflows/deploy-user.yml +++ b/.github/workflows/deploy-user.yml @@ -3,7 +3,7 @@ name: Dev User CI/CD on: push: branches: - - develop + - legacy paths: - 'nowait-app-user-api/**' - 'nowait-common/**' diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 32947253..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - mysql: - image: mysql:8.0 - container_name: mysql_nowait_container - environment: - MYSQL_ROOT_PASSWORD: gtable1118! - MYSQL_DATABASE: gtable_dev - MYSQL_USER: developer - MYSQL_PASSWORD: gtable1118! - ports: - - "3306:3306" - volumes: - - mysql_data:/var/lib/mysql - - nowait-app-admin-api: -# build: -# context: nowait-app-admin-api -# dockerfile: Dockerfile - image: jihuneddie/nowait_dev:admin - container_name: nowait-app-admin-api - ports: - - "8086:8085" - depends_on: - - mysql - volumes: - - ./nowait-app-admin-api:/app - restart: always - - nowait-app-user-api: -# build: -# context: nowait-app-user-api -# dockerfile: Dockerfile - image: jihuneddie/nowait_dev:user - container_name: nowait-app-user-api - ports: - - "8082:8081" - depends_on: - - mysql - volumes: - - ./nowait-app-user-api:/app - restart: always - -volumes: - mysql_data: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..363e737d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,3 +10,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 07db6d34..8e25d58f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,3 +11,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index b0740906..bbb99588 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -17,8 +17,8 @@ else fi echo "3. start container" -sudo docker-compose -p nowait_dev pull nowait-app-admin-api -sudo docker-compose -p nowait_dev up -d nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -p nowait_dev pull nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -p nowait_dev up -d nowait-app-admin-api echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 0b8d7d9a..aa537c8b 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -17,8 +17,8 @@ else fi echo "3. start container" -sudo docker-compose -p nowait_dev pull nowait-app-user-api -sudo docker-compose -p nowait_dev up -d nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -p nowait_dev pull nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -p nowait_dev up -d nowait-app-user-api echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From d6440f4f935e972cf8321fc7d73cd6dbbed2c803 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 9 Jul 2025 13:02:17 +0900 Subject: [PATCH 151/685] =?UTF-8?q?feat:=20docker=20compose=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=88=84=EB=9D=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.admin.yml | 27 +++++++++++++++++++ docker-compose.user.yml | 27 +++++++++++++++++++ .../java/com/nowait/ApiAdminApplication.java | 1 - .../java/com/nowait/ApiUserApplication.java | 1 - 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 docker-compose.admin.yml create mode 100644 docker-compose.user.yml diff --git a/docker-compose.admin.yml b/docker-compose.admin.yml new file mode 100644 index 00000000..d42dcaf6 --- /dev/null +++ b/docker-compose.admin.yml @@ -0,0 +1,27 @@ +services: + mysql: + image: mysql:8.0 + container_name: mysql_nowait_container + environment: + MYSQL_ROOT_PASSWORD: gtable1118! + MYSQL_DATABASE: gtable_dev + MYSQL_USER: developer + MYSQL_PASSWORD: gtable1118! + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + + nowait-app-admin-api: + image: jihuneddie/nowait_dev:admin + container_name: nowait-app-admin-api + ports: + - "8085:8085" + depends_on: + - mysql + volumes: + - ./nowait-app-admin-api:/app + restart: always + +volumes: + mysql_data: diff --git a/docker-compose.user.yml b/docker-compose.user.yml new file mode 100644 index 00000000..0e14807a --- /dev/null +++ b/docker-compose.user.yml @@ -0,0 +1,27 @@ +services: + mysql: + image: mysql:8.0 + container_name: mysql_nowait_container + environment: + MYSQL_ROOT_PASSWORD: gtable1118! + MYSQL_DATABASE: gtable_dev + MYSQL_USER: developer + MYSQL_PASSWORD: gtable1118! + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + + nowait-app-user-api: + image: jihuneddie/nowait_dev:user + container_name: nowait-app-user-api + ports: + - "8081:8081" + depends_on: + - mysql + volumes: + - ./nowait-app-user-api:/app + restart: always + +volumes: + mysql_data: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 363e737d..1098599d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -10,4 +10,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 8e25d58f..07db6d34 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -11,4 +11,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From 3d5da18d3b0dce805cdf204c6add0fb68e90b8fb Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:13:03 +0900 Subject: [PATCH 152/685] =?UTF-8?q?Delete=20.github/ISSUE=5FTEMPLATE/?= =?UTF-8?q?=E1=84=87=E1=85=A2=E1=86=A8=E1=84=8B=E1=85=A6=E1=86=AB=E1=84=83?= =?UTF-8?q?=E1=85=B3-=E1=84=8B=E1=85=B5=E1=84=89=E1=85=B2.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...204\213\341\205\265\341\204\211\341\205\262.md" | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 ".github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" diff --git "a/.github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" "b/.github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" deleted file mode 100644 index f728915f..00000000 --- "a/.github/ISSUE_TEMPLATE/\341\204\207\341\205\242\341\206\250\341\204\213\341\205\246\341\206\253\341\204\203\341\205\263-\341\204\213\341\205\265\341\204\211\341\205\262.md" +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: 백엔드 이슈 -about: 백엔드와 관련된 이슈 -title: "[백엔드] " -labels: Backend -assignees: '' - ---- - -# 이슈 내용 - - -# 작업 목록 -- [ ] From 10973be5c0a228027381d2a6b53ed942d4dac867 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:38:02 +0900 Subject: [PATCH 153/685] =?UTF-8?q?feat(StorePayment):=20StorePayment=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storePayment/entity/StorePayment.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java new file mode 100644 index 00000000..1cf1a6da --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java @@ -0,0 +1,57 @@ +package com.nowait.domaincorerdb.storePayment.entity; + +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Table(name = "store_payments") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SuperBuilder +public class StorePayment extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long paymentMethodId; + + @Column(name = "store_id", nullable = false) + private Long storeId; + + @Column(name = "toss_url",length = 500) + private String tossUrl; + + @Column(name = "kakao_pay_url", length = 500) + private String kakaoPayUrl; + + @Column(name = "naver_pay_url", length = 500) + private String naverPayUrl; + + public StorePayment(LocalDateTime createdAt, Long paymentMethodId, Long storeId, String tossUrl, String kakaoPayUrl, String naverPayUrl) { + super(createdAt); + this.paymentMethodId = paymentMethodId; + this.storeId = storeId; + this.tossUrl = tossUrl; + this.kakaoPayUrl = kakaoPayUrl; + this.naverPayUrl = naverPayUrl; + } + + public void updatePaymentMethodUrl(String tossUrl, String kakaoPayUrl, String naverPayUrl) { + if (tossUrl != null) this.tossUrl = tossUrl; + if (kakaoPayUrl != null) this.kakaoPayUrl = kakaoPayUrl; + if (naverPayUrl != null) this.naverPayUrl = naverPayUrl; + } +} From e28ed57baf5e7cfd1b23c6315b5b8fe0cd32fe8c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:38:15 +0900 Subject: [PATCH 154/685] =?UTF-8?q?feat(StorePayment):=20StorePayment=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java new file mode 100644 index 00000000..04fd65e6 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -0,0 +1,76 @@ +package com.nowait.applicationadmin.storePayment.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateRequest; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; +import com.nowait.applicationadmin.storePayment.service.StorePaymentService; +import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.user.entity.MemberDetails; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Tag(name = "Store Payment API", description = "주점 결제 정보 API") +@RestController +@RequestMapping("admin/store-payments") +@RequiredArgsConstructor +@Slf4j +public class StorePaymentController { + + private final StorePaymentService storePaymentService; + + @PostMapping("/create") + @Operation(summary = "주점 결제 Url 등록 및 생성", description = "새로운 주점 결제 정보를 생성합니다.") + @ApiResponse(responseCode = "201", description = "주점 결제 정보 생성 성공") + public ResponseEntity createStorePayment(@Valid @RequestBody StorePaymentCreateRequest request, @AuthenticationPrincipal MemberDetails memberDetails) { + StorePaymentCreateResponse response = storePaymentService.createStorePayment(request, memberDetails); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success( + response + ) + ); + } + + @GetMapping() + @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") + public ResponseEntity getStorePaymentByStoreId(@AuthenticationPrincipal MemberDetails memberDetails) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storePaymentService.getStorePaymentByStoreId(memberDetails) + ) + ); + } + + @PatchMapping("/update") + @Operation(summary = "주점 결제 정보 수정", description = "주점 결제 정보를 수정합니다.") + @ApiResponse(responseCode = "200", description = "주점 결제 정보 수정 성공") + public ResponseEntity updateStorePayment(@Valid @RequestBody StorePaymentUpdateRequest request, @AuthenticationPrincipal MemberDetails memberDetails) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storePaymentService.updateStorePayment(request, memberDetails) + ) + ); + } +} From fef6cf0ba80501093b18f40c856705b067b11c19 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:38:18 +0900 Subject: [PATCH 155/685] =?UTF-8?q?feat(StorePayment):=20StorePayment=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/store/controller/StoreController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java index 2d3a6aae..d59795a9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreController.java @@ -54,12 +54,12 @@ public ResponseEntity createStore(@Valid @RequestBody StoreCreateRequest requ @GetMapping("/{storeId}") @Operation(summary = "주점 조회", description = "주점 ID로 주점을 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 조회 성공") - public ResponseEntity getStoreById(@PathVariable Long storeId,@AuthenticationPrincipal MemberDetails memberDetails) { + public ResponseEntity getStoreById(@PathVariable Long storeId, @AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - storeService.getStoreByStoreId(storeId,memberDetails) + storeService.getStoreByStoreId(storeId, memberDetails) ) ); } @@ -84,7 +84,7 @@ public ResponseEntity updateStore( @DeleteMapping("/{storeId}") @Operation(summary = "주점 삭제", description = "주점 ID로 주점을 삭제합니다.") @ApiResponse(responseCode = "200", description = "주점 삭제 성공") - public ResponseEntity deleteStore(@PathVariable Long storeId,@AuthenticationPrincipal MemberDetails memberDetails) { + public ResponseEntity deleteStore(@PathVariable Long storeId, @AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .ok() .body( From 50e460ebe702894fc06abe3cfb43907904b293e2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:38:57 +0900 Subject: [PATCH 156/685] =?UTF-8?q?feat(StorePayment):=20StorePaymentServi?= =?UTF-8?q?ce=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20StorePaymentServiceImpl?= =?UTF-8?q?=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StorePaymentService.java | 14 ++++ .../service/StorePaymentServiceImpl.java | 81 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java new file mode 100644 index 00000000..b0324dab --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java @@ -0,0 +1,14 @@ +package com.nowait.applicationadmin.storePayment.service; + +import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateRequest; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentReadDto; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; +import com.nowait.domaincorerdb.user.entity.MemberDetails; + +public interface StorePaymentService { + + StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails); + StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails); + StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails); +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java new file mode 100644 index 00000000..3b923048 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -0,0 +1,81 @@ +package com.nowait.applicationadmin.storePayment.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateRequest; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentReadDto; +import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; +import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; +import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storePayment.repository.StorePaymentRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class StorePaymentServiceImpl implements StorePaymentService { + + private final StorePaymentRepository storePaymentRepository; + private final UserRepository userRepository; + + @Override + @Transactional + public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails) { + // if (request == null) throw new StoreParamEmptyException(); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long storeId = user.getStoreId(); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StoreViewUnauthorizedException(); + } + StorePayment toSave = request.toEntity(storeId); + StorePayment saved = storePaymentRepository.save(toSave); + + return StorePaymentCreateResponse.fromEntity(saved); + } + + @Override + @Transactional(readOnly = true) + public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) { + // if (storeId == null) throw new StoreParamEmptyException(); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long storeId = user.getStoreId(); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StoreViewUnauthorizedException(); + } + StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) + .orElseThrow(StoreNotFoundException::new); + + return StorePaymentReadDto.fromEntity(storePayment); + } + + @Override + @Transactional + public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { + if (request == null) throw new StoreParamEmptyException(); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long storeId = user.getStoreId(); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StoreViewUnauthorizedException(); + } + StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) + .orElseThrow(StoreNotFoundException::new); + + storePayment.updatePaymentMethodUrl( + request.getTossUrl(), + request.getKakaoPayUrl(), + request.getNaverPayUrl() + ); + storePaymentRepository.save(storePayment); + + return StorePaymentReadDto.fromEntity(storePayment); + } +} From eece16514c9fee762a228cc5dd6652cf40a55c96 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:39:08 +0900 Subject: [PATCH 157/685] =?UTF-8?q?feat(StorePayment):=20StorePaymentRepos?= =?UTF-8?q?itory=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/StorePaymentRepository.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java new file mode 100644 index 00000000..bb2aa8bf --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java @@ -0,0 +1,13 @@ +package com.nowait.domaincorerdb.storePayment.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; + +@Repository +public interface StorePaymentRepository extends JpaRepository { + Optional findByStoreId(Long storeId); +} From 1ad00bcc457f8d793c5a4c210b82996fbf8ee626 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:39:29 +0900 Subject: [PATCH 158/685] =?UTF-8?q?feat(StorePayment):=20StorePaymentCreat?= =?UTF-8?q?e=20=EA=B4=80=EB=A0=A8=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/StorePaymentCreateRequest.java | 27 +++++++++++++++ .../dto/StorePaymentCreateResponse.java | 33 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java new file mode 100644 index 00000000..c790ec5d --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java @@ -0,0 +1,27 @@ +package com.nowait.applicationadmin.storePayment.dto; + +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class StorePaymentCreateRequest { + + private String tossUrl; + private String kakaoPayUrl; + private String naverPayUrl; + + public StorePayment toEntity(Long storeId) { + return StorePayment.builder() + .storeId(storeId) + .tossUrl(tossUrl) + .kakaoPayUrl(kakaoPayUrl) + .naverPayUrl(naverPayUrl) + .build(); + } +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java new file mode 100644 index 00000000..50720432 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java @@ -0,0 +1,33 @@ +package com.nowait.applicationadmin.storePayment.dto; + +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StorePaymentCreateResponse { + + private Long paymentMethodId; + private Long storeId; + private String tossUrl; + private String kakaoPayUrl; + private String naverPayUrl; + private LocalDateTime createdAt; + + public static StorePaymentCreateResponse fromEntity(StorePayment storePayment) { + return StorePaymentCreateResponse.builder() + .paymentMethodId(storePayment.getPaymentMethodId()) + .storeId(storePayment.getStoreId()) + .tossUrl(storePayment.getTossUrl()) + .kakaoPayUrl(storePayment.getKakaoPayUrl()) + .naverPayUrl(storePayment.getNaverPayUrl()) + .createdAt(storePayment.getCreatedAt()) + .build(); + } +} From e4f66b97cb826653da4a77b28ceca795fdcb8a6e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:39:36 +0900 Subject: [PATCH 159/685] =?UTF-8?q?feat(StorePayment):=20StorePaymentRead?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storePayment/dto/StorePaymentReadDto.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java new file mode 100644 index 00000000..f27b92c3 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java @@ -0,0 +1,32 @@ +package com.nowait.applicationadmin.storePayment.dto; + +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StorePaymentReadDto { + private Long paymentMethodId; + private Long storeId; + private String tossUrl; + private String kakaoPayUrl; + private String naverPayUrl; + private LocalDateTime createdAt; + + public static StorePaymentReadDto fromEntity(StorePayment storePayment) { + return StorePaymentReadDto.builder() + .paymentMethodId(storePayment.getPaymentMethodId()) + .storeId(storePayment.getStoreId()) + .tossUrl(storePayment.getTossUrl()) + .kakaoPayUrl(storePayment.getKakaoPayUrl()) + .naverPayUrl(storePayment.getNaverPayUrl()) + .createdAt(storePayment.getCreatedAt()) + .build(); + } +} From 1fded2f380d1abec35eaebe4be1ce1065b6ff826 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 01:39:45 +0900 Subject: [PATCH 160/685] =?UTF-8?q?feat(StorePayment):=20StorePaymentUpdat?= =?UTF-8?q?e=20=EA=B4=80=EB=A0=A8=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/StorePaymentUpdateRequest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java new file mode 100644 index 00000000..71f4c979 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java @@ -0,0 +1,16 @@ +package com.nowait.applicationadmin.storePayment.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StorePaymentUpdateRequest { + private String tossUrl; + private String kakaoPayUrl; + private String naverPayUrl; +} From e02271481055698d4f53bf23681d82ae1cf4f09a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 11:57:07 +0900 Subject: [PATCH 161/685] =?UTF-8?q?feat(StorePayment):=20StorePayment=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/common/exception/ErrorMessage.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 02a107a1..48c56987 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -49,6 +49,16 @@ public enum ErrorMessage { STORE_DELETE_UNAUTHORIZED("주점 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "store005"), STORE_WAITING_DISABLED("해당 주점은 대기 비활성화된 주점입니다.", "store006"), + // storePayment + + STORE_PAYMENT_PARAMETER_EMPTY("주점 결제 생성 시 파라미터 정보가 없습니다.", "storePayment001"), + STORE_PAYMENT_NOT_FOUND("해당 주점 결제 정보를 찾을 수 없습니다.", "storePayment002"), + STORE_PAYMENT_VIEW_UNAUTHORIZED("주점 결제 정보 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment003"), + STORE_PAYMENT_CREATION_UNAUTHORIZED("주점 결제 정보 생성 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), + STORE_PAYMENT_UPDATE_UNAUTHORIZED("주점 결제 정보 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), + STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), + + // image IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), IMAGE_FILE_NOT_FOUND("이미지 파일을 업로드 해주세요", "image001"), From 716daded146530bb62c678fee03773720c25f11c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 11:57:27 +0900 Subject: [PATCH 162/685] =?UTF-8?q?feat(StorePayment):=20StorePayment=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreServiceImpl.java | 1 + .../service/StorePaymentServiceImpl.java | 18 +++++--- .../controller/StorePaymentController.java | 43 +++++++++++++++++++ .../storePayment/dto/StorePaymentReadDto.java | 32 ++++++++++++++ .../service/StorePaymentService.java | 7 +++ .../service/StorePaymentServiceImpl.java | 29 +++++++++++++ .../StorePaymentCreationUnauthorized.java | 10 +++++ .../StorePaymentNotFoundException.java | 9 ++++ .../StorePaymentParamEmptyException.java | 9 ++++ ...orePaymentUpdateUnauthorizedException.java | 9 ++++ ...StorePaymentViewUnauthorizedException.java | 9 ++++ 11 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index 0dec4fd8..7944746c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -2,6 +2,7 @@ import java.util.List; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index 3b923048..ea615d3f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -10,8 +10,11 @@ import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; -import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storePayment.exception.StorePaymentCreationUnauthorized; +import com.nowait.domaincorerdb.storePayment.exception.StorePaymentParamEmptyException; +import com.nowait.domaincorerdb.storePayment.exception.StorePaymentUpdateUnauthorizedException; +import com.nowait.domaincorerdb.storePayment.exception.StorePaymentViewUnauthorizedException; import com.nowait.domaincorerdb.storePayment.repository.StorePaymentRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; @@ -30,11 +33,12 @@ public class StorePaymentServiceImpl implements StorePaymentService { @Override @Transactional public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails) { - // if (request == null) throw new StoreParamEmptyException(); + if (request == null) throw new StorePaymentParamEmptyException(); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreViewUnauthorizedException(); + throw new StorePaymentCreationUnauthorized(); } StorePayment toSave = request.toEntity(storeId); StorePayment saved = storePaymentRepository.save(toSave); @@ -45,11 +49,12 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r @Override @Transactional(readOnly = true) public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) { - // if (storeId == null) throw new StoreParamEmptyException(); + if (memberDetails == null) throw new StorePaymentParamEmptyException(); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreViewUnauthorizedException(); + throw new StorePaymentViewUnauthorizedException(); } StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) .orElseThrow(StoreNotFoundException::new); @@ -61,10 +66,11 @@ public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) @Transactional public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { if (request == null) throw new StoreParamEmptyException(); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreViewUnauthorizedException(); + throw new StorePaymentUpdateUnauthorizedException(); } StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) .orElseThrow(StoreNotFoundException::new); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java new file mode 100644 index 00000000..b02020c5 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java @@ -0,0 +1,43 @@ +package com.nowait.applicationuser.storePayment.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationuser.storePayment.service.StorePaymentService; +import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.user.entity.MemberDetails; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Tag(name = "Store Payment API", description = "주점 결제 정보 API") +@RestController +@RequestMapping("v1/store-payments") +@RequiredArgsConstructor +@Slf4j +public class StorePaymentController { + + private final StorePaymentService storePaymentService; + + @GetMapping(("/{storeId}")) + @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") + public ResponseEntity getStorePaymentByStoreId(@Valid @PathVariable Long storeId) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storePaymentService.getStorePaymentByStoreId(storeId) + ) + ); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java new file mode 100644 index 00000000..198aa78d --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java @@ -0,0 +1,32 @@ +package com.nowait.applicationuser.storePayment.dto; + +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StorePaymentReadDto { + private Long paymentMethodId; + private Long storeId; + private String tossUrl; + private String kakaoPayUrl; + private String naverPayUrl; + private LocalDateTime createdAt; + + public static StorePaymentReadDto fromEntity(StorePayment storePayment) { + return StorePaymentReadDto.builder() + .paymentMethodId(storePayment.getPaymentMethodId()) + .storeId(storePayment.getStoreId()) + .tossUrl(storePayment.getTossUrl()) + .kakaoPayUrl(storePayment.getKakaoPayUrl()) + .naverPayUrl(storePayment.getNaverPayUrl()) + .createdAt(storePayment.getCreatedAt()) + .build(); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java new file mode 100644 index 00000000..fdccb0e3 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java @@ -0,0 +1,7 @@ +package com.nowait.applicationuser.storePayment.service; + +import com.nowait.applicationuser.storePayment.dto.StorePaymentReadDto; + +public interface StorePaymentService { + StorePaymentReadDto getStorePaymentByStoreId(Long storeId); +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java new file mode 100644 index 00000000..4a8a9660 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java @@ -0,0 +1,29 @@ +package com.nowait.applicationuser.storePayment.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationuser.storePayment.dto.StorePaymentReadDto; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storePayment.exception.StorePaymentParamEmptyException; +import com.nowait.domaincorerdb.storePayment.repository.StorePaymentRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class StorePaymentServiceImpl implements StorePaymentService { + + private final StorePaymentRepository storePaymentRepository; + + @Override + @Transactional(readOnly = true) + public StorePaymentReadDto getStorePaymentByStoreId(Long storeId) { + if (storeId == null) throw new StorePaymentParamEmptyException(); + + StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) + .orElseThrow(StoreNotFoundException::new); + + return StorePaymentReadDto.fromEntity(storePayment); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java new file mode 100644 index 00000000..ca4a3d21 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.storePayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentCreationUnauthorized extends RuntimeException { + + public StorePaymentCreationUnauthorized() { + super(ErrorMessage.STORE_PAYMENT_CREATION_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java new file mode 100644 index 00000000..0ef9ab96 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storePayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentNotFoundException extends RuntimeException { + public StorePaymentNotFoundException(String message) { + super(ErrorMessage.STORE_PAYMENT_PARAMETER_EMPTY.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java new file mode 100644 index 00000000..a18525cb --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storePayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentParamEmptyException extends RuntimeException { + public StorePaymentParamEmptyException() { + super(ErrorMessage.STORE_PAYMENT_PARAMETER_EMPTY.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java new file mode 100644 index 00000000..1039f30e --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storePayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentUpdateUnauthorizedException extends RuntimeException { + public StorePaymentUpdateUnauthorizedException() { + super(ErrorMessage.STORE_PAYMENT_UPDATE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java new file mode 100644 index 00000000..a3415d10 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storePayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentViewUnauthorizedException extends RuntimeException { + public StorePaymentViewUnauthorizedException() { + super(ErrorMessage.STORE_PAYMENT_VIEW_UNAUTHORIZED.getMessage()); + } +} From f4ea0114a410a3b0a9dce83938827995c410551a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 11:57:44 +0900 Subject: [PATCH 163/685] =?UTF-8?q?chore:=20=EC=A4=91=EB=B3=B5=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27\224\353\223\234-\354\235\264\354\212\210.md" | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 ".github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" diff --git "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" deleted file mode 100644 index f728915f..00000000 --- "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: 백엔드 이슈 -about: 백엔드와 관련된 이슈 -title: "[백엔드] " -labels: Backend -assignees: '' - ---- - -# 이슈 내용 - - -# 작업 목록 -- [ ] From 7b81e4ad02bad21e94eada8b4140dd6340c47da1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 12:17:20 +0900 Subject: [PATCH 164/685] =?UTF-8?q?feat(StorePayment):=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=90=20=EA=B2=B0=EC=A0=9C=20=EC=A0=95=EB=B3=B4=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/common/exception/ErrorMessage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 48c56987..b64c5e37 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -57,6 +57,7 @@ public enum ErrorMessage { STORE_PAYMENT_CREATION_UNAUTHORIZED("주점 결제 정보 생성 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), STORE_PAYMENT_UPDATE_UNAUTHORIZED("주점 결제 정보 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), + STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment006"), // image From 66b012ba2b3d33eebe4b129f71c6a17c5043bfb9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 12:18:54 +0900 Subject: [PATCH 165/685] =?UTF-8?q?feat(StorePayment):=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/StorePaymentCreateRequest.java | 3 +-- .../dto/StorePaymentCreateResponse.java | 2 +- .../storePayment/dto/StorePaymentReadDto.java | 2 +- .../service/StorePaymentServiceImpl.java | 27 +++++++++++-------- .../storePayment/dto/StorePaymentReadDto.java | 2 +- .../service/StorePaymentServiceImpl.java | 6 ++--- .../storePayment/entity/StorePayment.java | 2 +- .../StorePaymentNotFoundException.java | 4 +-- .../StorePaymentParamEmptyException.java | 2 +- ...orePaymentUpdateUnauthorizedException.java | 2 +- ...StorePaymentViewUnauthorizedException.java | 2 +- .../repository/StorePaymentRepository.java | 4 +-- 12 files changed, 31 insertions(+), 27 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java index c790ec5d..4a292ce3 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java @@ -1,8 +1,7 @@ package com.nowait.applicationadmin.storePayment.dto; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java index 50720432..250a6b1f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java index f27b92c3..329d799a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index ea615d3f..2b70b342 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -10,12 +10,14 @@ import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; -import com.nowait.domaincorerdb.storePayment.exception.StorePaymentCreationUnauthorized; -import com.nowait.domaincorerdb.storePayment.exception.StorePaymentParamEmptyException; -import com.nowait.domaincorerdb.storePayment.exception.StorePaymentUpdateUnauthorizedException; -import com.nowait.domaincorerdb.storePayment.exception.StorePaymentViewUnauthorizedException; -import com.nowait.domaincorerdb.storePayment.repository.StorePaymentRepository; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentCreationUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentUpdateUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentViewUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.repository.StorePaymentRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -37,8 +39,11 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentCreationUnauthorized(); + if (storePaymentRepository.findByStoreId(storeId).isPresent()) { + throw new StorePaymentAlreadyExistsException(); + } + if (!Role.SUPER_ADMIN.equals(user.getRole())) { + throw new StorePaymentCreationUnauthorizedException(); } StorePayment toSave = request.toEntity(storeId); StorePayment saved = storePaymentRepository.save(toSave); @@ -53,7 +58,7 @@ public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + if (!Role.SUPER_ADMIN.equals(user.getRole())) { throw new StorePaymentViewUnauthorizedException(); } StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) @@ -65,11 +70,11 @@ public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) @Override @Transactional public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { - if (request == null) throw new StoreParamEmptyException(); + if (request == null) throw new StorePaymentParamEmptyException(); User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + if (!Role.SUPER_ADMIN.equals(user.getRole())) { throw new StorePaymentUpdateUnauthorizedException(); } StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java index 198aa78d..7c405bf9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java index 4a8a9660..44a8de5c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java @@ -4,9 +4,9 @@ import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.storePayment.dto.StorePaymentReadDto; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; -import com.nowait.domaincorerdb.storePayment.exception.StorePaymentParamEmptyException; -import com.nowait.domaincorerdb.storePayment.repository.StorePaymentRepository; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; +import com.nowait.domaincorerdb.storepayment.repository.StorePaymentRepository; import lombok.RequiredArgsConstructor; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java index 1cf1a6da..effb50d8 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.storePayment.entity; +package com.nowait.domaincorerdb.storepayment.entity; import java.time.LocalDateTime; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java index 0ef9ab96..af83a4db 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java @@ -1,9 +1,9 @@ -package com.nowait.domaincorerdb.storePayment.exception; +package com.nowait.domaincorerdb.storepayment.exception; import com.nowait.common.exception.ErrorMessage; public class StorePaymentNotFoundException extends RuntimeException { - public StorePaymentNotFoundException(String message) { + public StorePaymentNotFoundException() { super(ErrorMessage.STORE_PAYMENT_PARAMETER_EMPTY.getMessage()); } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java index a18525cb..83b62dba 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.storePayment.exception; +package com.nowait.domaincorerdb.storepayment.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java index 1039f30e..32d1c85a 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.storePayment.exception; +package com.nowait.domaincorerdb.storepayment.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java index a3415d10..cec829ec 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.storePayment.exception; +package com.nowait.domaincorerdb.storepayment.exception; import com.nowait.common.exception.ErrorMessage; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java index bb2aa8bf..3a92bc09 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/repository/StorePaymentRepository.java @@ -1,11 +1,11 @@ -package com.nowait.domaincorerdb.storePayment.repository; +package com.nowait.domaincorerdb.storepayment.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.nowait.domaincorerdb.storePayment.entity.StorePayment; +import com.nowait.domaincorerdb.storepayment.entity.StorePayment; @Repository public interface StorePaymentRepository extends JpaRepository { From 3f3265fbb74a132f5d71b1095f79a78b8df31d16 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 12:19:15 +0900 Subject: [PATCH 166/685] =?UTF-8?q?feat(StorePayment):=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=90=20=EA=B2=B0=EC=A0=9C=20=EC=A0=95=EB=B3=B4=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 2 +- .../exception/StorePaymentAlreadyExistsException.java | 9 +++++++++ .../StorePaymentCreationUnauthorizedException.java | 10 ++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentAlreadyExistsException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 04fd65e6..5785b637 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -49,7 +49,7 @@ public ResponseEntity createStorePayment(@Valid @RequestBody StorePaymentCrea } @GetMapping() - @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") + @Operation(summary = "주점 결제 정보 조회", description = "인증된 사용자의 주점 결제 정보를 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") public ResponseEntity getStorePaymentByStoreId(@AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentAlreadyExistsException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentAlreadyExistsException.java new file mode 100644 index 00000000..609ba342 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentAlreadyExistsException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storepayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentAlreadyExistsException extends RuntimeException { + public StorePaymentAlreadyExistsException() { + super(ErrorMessage.STORE_PAYMENT_ALREADY_EXISTS.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorizedException.java new file mode 100644 index 00000000..ad458a24 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorizedException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.storepayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentCreationUnauthorizedException extends RuntimeException { + + public StorePaymentCreationUnauthorizedException() { + super(ErrorMessage.STORE_PAYMENT_CREATION_UNAUTHORIZED.getMessage()); + } +} From f576d684684df0c8b97bc927f21383ce54de2161 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 12:19:20 +0900 Subject: [PATCH 167/685] =?UTF-8?q?feat(StorePayment):=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=90=20=EA=B2=B0=EC=A0=9C=20=EC=A0=95=EB=B3=B4=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/StorePaymentCreationUnauthorized.java | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java deleted file mode 100644 index ca4a3d21..00000000 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorized.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nowait.domaincorerdb.storePayment.exception; - -import com.nowait.common.exception.ErrorMessage; - -public class StorePaymentCreationUnauthorized extends RuntimeException { - - public StorePaymentCreationUnauthorized() { - super(ErrorMessage.STORE_PAYMENT_CREATION_UNAUTHORIZED.getMessage()); - } -} From 71455dbb86c598c9b3f48c5c62b554abb24868a5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 12:20:13 +0900 Subject: [PATCH 168/685] =?UTF-8?q?feat(StorePayment):=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storePayment/service/StorePaymentService.java | 7 ------- .../controller/StorePaymentController.java | 6 ++---- .../dto/StorePaymentReadDto.java | 2 +- .../storepayment/service/StorePaymentService.java | 7 +++++++ .../service/StorePaymentServiceImpl.java | 4 ++-- 5 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/{storePayment => storepayment}/controller/StorePaymentController.java (84%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/{storePayment => storepayment}/dto/StorePaymentReadDto.java (93%) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/{storePayment => storepayment}/service/StorePaymentServiceImpl.java (89%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java deleted file mode 100644 index fdccb0e3..00000000 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.nowait.applicationuser.storePayment.service; - -import com.nowait.applicationuser.storePayment.dto.StorePaymentReadDto; - -public interface StorePaymentService { - StorePaymentReadDto getStorePaymentByStoreId(Long storeId); -} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java similarity index 84% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index b02020c5..e6a8582b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -1,16 +1,14 @@ -package com.nowait.applicationuser.storePayment.controller; +package com.nowait.applicationuser.storepayment.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.nowait.applicationuser.storePayment.service.StorePaymentService; +import com.nowait.applicationuser.storepayment.service.StorePaymentService; import com.nowait.common.api.ApiUtils; -import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java similarity index 93% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java index 7c405bf9..e0af8aab 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/dto/StorePaymentReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.storePayment.dto; +package com.nowait.applicationuser.storepayment.dto; import java.time.LocalDateTime; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java new file mode 100644 index 00000000..4db20e4b --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java @@ -0,0 +1,7 @@ +package com.nowait.applicationuser.storepayment.service; + +import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; + +public interface StorePaymentService { + StorePaymentReadDto getStorePaymentByStoreId(Long storeId); +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java similarity index 89% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java index 44a8de5c..7df519f6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java @@ -1,8 +1,8 @@ -package com.nowait.applicationuser.storePayment.service; +package com.nowait.applicationuser.storepayment.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationuser.storePayment.dto.StorePaymentReadDto; +import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; From 41d3068d384f2bb03f1bce67b155a4287dece335 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 11 Jul 2025 12:29:16 +0900 Subject: [PATCH 169/685] =?UTF-8?q?feat(StorePayment):=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 2 +- .../service/StorePaymentServiceImpl.java | 12 +++++------- .../controller/StorePaymentController.java | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 5785b637..df8bc44a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -64,7 +64,7 @@ public ResponseEntity getStorePaymentByStoreId(@AuthenticationPrincipal Membe @PatchMapping("/update") @Operation(summary = "주점 결제 정보 수정", description = "주점 결제 정보를 수정합니다.") @ApiResponse(responseCode = "200", description = "주점 결제 정보 수정 성공") - public ResponseEntity updateStorePayment(@Valid @RequestBody StorePaymentUpdateRequest request, @AuthenticationPrincipal MemberDetails memberDetails) { + public ResponseEntity updateStorePayment(@RequestBody StorePaymentUpdateRequest request, @AuthenticationPrincipal MemberDetails memberDetails) { return ResponseEntity .status(HttpStatus.OK) .body( diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index 2b70b342..5eb6fbca 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -8,8 +8,6 @@ import com.nowait.applicationadmin.storePayment.dto.StorePaymentReadDto; import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; import com.nowait.common.enums.Role; -import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; -import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentCreationUnauthorizedException; @@ -42,7 +40,7 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r if (storePaymentRepository.findByStoreId(storeId).isPresent()) { throw new StorePaymentAlreadyExistsException(); } - if (!Role.SUPER_ADMIN.equals(user.getRole())) { + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new StorePaymentCreationUnauthorizedException(); } StorePayment toSave = request.toEntity(storeId); @@ -58,11 +56,11 @@ public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole())) { + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new StorePaymentViewUnauthorizedException(); } StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) - .orElseThrow(StoreNotFoundException::new); + .orElseThrow(StorePaymentNotFoundException::new); return StorePaymentReadDto.fromEntity(storePayment); } @@ -74,11 +72,11 @@ public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole())) { + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new StorePaymentUpdateUnauthorizedException(); } StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) - .orElseThrow(StoreNotFoundException::new); + .orElseThrow(StorePaymentNotFoundException::new); storePayment.updatePaymentMethodUrl( request.getTossUrl(), diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index e6a8582b..c784ac1f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -29,7 +29,7 @@ public class StorePaymentController { @GetMapping(("/{storeId}")) @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") - public ResponseEntity getStorePaymentByStoreId(@Valid @PathVariable Long storeId) { + public ResponseEntity getStorePaymentByStoreId(@PathVariable Long storeId) { return ResponseEntity .status(HttpStatus.OK) .body( From 1e6141a48a9c7ec46cd0f4d5567f13286675c684 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:34:25 +0900 Subject: [PATCH 170/685] =?UTF-8?q?refactor:=20ApiResult=EC=97=90=20error?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20null=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/common/api/ApiResult.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nowait-common/src/main/java/com/nowait/common/api/ApiResult.java b/nowait-common/src/main/java/com/nowait/common/api/ApiResult.java index 73509f56..17247f42 100644 --- a/nowait-common/src/main/java/com/nowait/common/api/ApiResult.java +++ b/nowait-common/src/main/java/com/nowait/common/api/ApiResult.java @@ -19,7 +19,5 @@ public T getResponse() { return response; } - public ApiError getError() { - return error; - } + // public ApiError getError() { return success ? null : error; } } From de18a50ebda555980e711d1fa26d4c09d0458f42 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:36:05 +0900 Subject: [PATCH 171/685] =?UTF-8?q?chore(build):=20QueryDsl=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-domain/domain-core-rdb/build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/build.gradle b/nowait-domain/domain-core-rdb/build.gradle index 01bc023b..daa8ad3d 100644 --- a/nowait-domain/domain-core-rdb/build.gradle +++ b/nowait-domain/domain-core-rdb/build.gradle @@ -31,6 +31,12 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-validation' + //QueryDsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // SWAGGER implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' @@ -38,7 +44,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.26' // Jackson - api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' // Jackson 핵심 + api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' testImplementation 'org.junit.jupiter:junit-jupiter' From ed760cc53cea52e2fc8eb41d1e6fab9ed08694d6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:38:54 +0900 Subject: [PATCH 172/685] =?UTF-8?q?feat(Order):=20=EB=A7=A4=EC=B6=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20sales=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 6c2b845d..fe7144f6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -17,6 +17,7 @@ import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.applicationadmin.order.service.OrderService; import com.nowait.common.api.ApiUtils; +import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; @@ -63,4 +64,19 @@ public ResponseEntity updateOrderStatus( .status(HttpStatus.OK) .body(ApiUtils.success(response)); } + + @GetMapping("/sales") + @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") + @ApiResponse(responseCode = "200", description = "오늘의 매출 조회 성공") + public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails memberDetails) { + OrderSalesSumResponse sales = orderService.getSaleSumByStoreId(memberDetails); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + sales + ) + ); + } } From acda7bfd72926d1e55052f89bf8e934ea2210a68 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:40:03 +0900 Subject: [PATCH 173/685] =?UTF-8?q?feat(Order):=20=EB=A7=A4=EC=B6=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20getSaleSumBy?= =?UTF-8?q?StoreId=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 19ec26c7..0e23034e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -9,6 +9,7 @@ import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; @@ -56,4 +57,16 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus userOrder.updateStatus(newStatus); return OrderStatusUpdateResponseDto.fromEntity(userOrder); } + + @Transactional(readOnly = true) + public OrderSalesSumResponse getSaleSumByStoreId(MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long storeId = user.getStoreId(); + + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new OrderViewUnauthorizedException(); + } + + return orderRepository.findSalesSumByStoreId(storeId); + } } From 96b96bb86d37ee81d35531f8c68e67b3574815e7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:40:19 +0900 Subject: [PATCH 174/685] =?UTF-8?q?feat(Order):=20=EB=A7=A4=EC=B6=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20OrderSalesSu?= =?UTF-8?q?mResponse=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/dto/OrderSalesSumResponse.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java new file mode 100644 index 00000000..5dc279e8 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java @@ -0,0 +1,20 @@ +package com.nowait.domaincorerdb.order.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class OrderSalesSumResponse { + Long storeId; + Integer todaySalesSum; + Integer yesterdaySalesSum; + Integer previousDaySales; + + public OrderSalesSumResponse(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, Integer previousDaySales) { + this.storeId = storeId; + this.todaySalesSum = todaySalesSum; + this.yesterdaySalesSum = yesterdaySalesSum; + this.previousDaySales = previousDaySales; + } +} From 26d87dd06a25a38a687995a6b550c68ee98338e4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:41:09 +0900 Subject: [PATCH 175/685] feat(Order): extends OrderCustomRepository --- .../domaincorerdb/order/repository/OrderRepository.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index 96db42c4..ff051a83 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -8,11 +8,9 @@ import com.nowait.domaincorerdb.order.entity.UserOrder; @Repository -public interface OrderRepository extends JpaRepository { +public interface OrderRepository extends JpaRepository, OrderCustomRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); List findAllByStore_StoreId(Long storeId); - - } From 804d127d491008f0a5658fed18a73883d42c7d8b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:41:32 +0900 Subject: [PATCH 176/685] =?UTF-8?q?feat(Order):=20QueryDsl=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20config=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/config/QueryDslConfig.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java new file mode 100644 index 00000000..f75d497b --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java @@ -0,0 +1,20 @@ +package com.nowait.domaincorerdb.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.querydsl.jpa.impl.JPAQueryFactory; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@Configuration +public class QueryDslConfig { + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} From 33e68c1754576c99166b81e41318cf61a93b50bf Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:41:48 +0900 Subject: [PATCH 177/685] =?UTF-8?q?feat(Order):=20=EB=A7=A4=EC=B6=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20findSalesSumByStoreId=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/OrderCustomRepository.java | 8 ++ .../repository/OrderCustomRepositoryImpl.java | 76 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java new file mode 100644 index 00000000..c02d1a77 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java @@ -0,0 +1,8 @@ +package com.nowait.domaincorerdb.order.repository; + +import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; + +public interface OrderCustomRepository { + + OrderSalesSumResponse findSalesSumByStoreId(Long storeId); +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java new file mode 100644 index 00000000..c828a64e --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java @@ -0,0 +1,76 @@ +package com.nowait.domaincorerdb.order.repository; + +import static com.nowait.domaincorerdb.order.entity.OrderStatus.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; +import com.nowait.domaincorerdb.order.entity.QUserOrder; +import com.querydsl.jpa.impl.JPAQueryFactory; + +public class OrderCustomRepositoryImpl implements OrderCustomRepository { + + private final JPAQueryFactory queryFactory; + + public OrderCustomRepositoryImpl(JPAQueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + QUserOrder u = QUserOrder.userOrder; + + @Override + public OrderSalesSumResponse findSalesSumByStoreId(Long storeId) { + // 1. 날짜 기준 설정 (시작은 자정, 끝은 다음 날 자정) + LocalDate today = LocalDate.now(); + LocalDate yesterday = today.minusDays(1); + + LocalDateTime todayStart = today.atStartOfDay(); + LocalDateTime todayEnd = today.plusDays(1).atStartOfDay(); // 내일 00:00:00 + + LocalDateTime yesterdayStart = yesterday.atStartOfDay(); + LocalDateTime yesterdayEnd = today.atStartOfDay(); + + // 2. 오늘 매출 합산 + Integer todaySum = queryFactory + .select(u.totalPrice.sum()) + .from(u) + .where( + u.store.storeId.eq(storeId), + u.createdAt.goe(todayStart), + u.createdAt.lt(todayEnd), + u.status.eq(COOKED) + ) + .fetchOne(); + + // 3. 어제 매출 합산 + Integer yesterdaySum = queryFactory + .select(u.totalPrice.sum()) + .from(u) + .where( + u.store.storeId.eq(storeId), + u.createdAt.goe(yesterdayStart), + u.createdAt.lt(yesterdayEnd), + u.status.eq(COOKED) + ) + .fetchOne(); + + Integer previousDaySales = queryFactory + .select(u.totalPrice.sum()) + .from(u) + .where( + u.store.storeId.eq(storeId), + u.createdAt.lt(yesterdayEnd), + u.status.eq(COOKED) + ) + .fetchOne(); + + // null 방어 처리 + if (todaySum == null) todaySum = 0; + if (yesterdaySum == null) yesterdaySum = 0; + if (previousDaySales == null) previousDaySales = 0; + + // 4. 응답 객체 생성 + return new OrderSalesSumResponse(storeId, todaySum, yesterdaySum, previousDaySales); + } +} From f03d1ecd38fa568ad41c42a1fdc5f8fa1955e420 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 15:42:13 +0900 Subject: [PATCH 178/685] =?UTF-8?q?refactor(storepayment):=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 10 +++++----- .../storePayment/dto/StorePaymentCreateRequest.java | 3 ++- .../storePayment/dto/StorePaymentCreateResponse.java | 2 +- .../storePayment/dto/StorePaymentReadDto.java | 2 +- .../storePayment/dto/StorePaymentUpdateRequest.java | 2 +- .../storePayment/service/StorePaymentService.java | 10 +++++----- .../storePayment/service/StorePaymentServiceImpl.java | 10 +++++----- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index df8bc44a..82c1784a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -1,4 +1,4 @@ -package com.nowait.applicationadmin.storePayment.controller; +package com.nowait.applicationadmin.storepayment.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -10,10 +10,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateRequest; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateResponse; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; -import com.nowait.applicationadmin.storePayment.service.StorePaymentService; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentUpdateRequest; +import com.nowait.applicationadmin.storepayment.service.StorePaymentService; import com.nowait.common.api.ApiUtils; import com.nowait.domaincorerdb.user.entity.MemberDetails; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java index 4a292ce3..13801ccb 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java @@ -1,4 +1,5 @@ -package com.nowait.applicationadmin.storePayment.dto; +package com.nowait.applicationadmin.storepayment.dto; + import com.nowait.domaincorerdb.storepayment.entity.StorePayment; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java index 250a6b1f..47bead90 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java @@ -1,4 +1,4 @@ -package com.nowait.applicationadmin.storePayment.dto; +package com.nowait.applicationadmin.storepayment.dto; import java.time.LocalDateTime; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java index 329d799a..04f2e0f6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java @@ -1,4 +1,4 @@ -package com.nowait.applicationadmin.storePayment.dto; +package com.nowait.applicationadmin.storepayment.dto; import java.time.LocalDateTime; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java index 71f4c979..50320aab 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java @@ -1,4 +1,4 @@ -package com.nowait.applicationadmin.storePayment.dto; +package com.nowait.applicationadmin.storepayment.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java index b0324dab..4cf3c76f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java @@ -1,9 +1,9 @@ -package com.nowait.applicationadmin.storePayment.service; +package com.nowait.applicationadmin.storepayment.service; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateRequest; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateResponse; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentReadDto; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentReadDto; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentUpdateRequest; import com.nowait.domaincorerdb.user.entity.MemberDetails; public interface StorePaymentService { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index 5eb6fbca..ff95015d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -1,12 +1,12 @@ -package com.nowait.applicationadmin.storePayment.service; +package com.nowait.applicationadmin.storepayment.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateRequest; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentCreateResponse; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentReadDto; -import com.nowait.applicationadmin.storePayment.dto.StorePaymentUpdateRequest; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentReadDto; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentUpdateRequest; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; From 2ded7120e5a822a2e35a9f6c2b4826294aaa46a3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 14 Jul 2025 16:31:48 +0900 Subject: [PATCH 179/685] =?UTF-8?q?refactor(UserOrder):=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 88 +++++++++---------- .../order/service/OrderService.java | 19 ++-- ...Response.java => OrderSalesSumDetail.java} | 5 +- .../repository/OrderCustomRepository.java | 4 +- .../repository/OrderCustomRepositoryImpl.java | 6 +- .../order/repository/OrderRepository.java | 4 +- 6 files changed, 64 insertions(+), 62 deletions(-) rename nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/{OrderSalesSumResponse.java => OrderSalesSumDetail.java} (69%) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index fe7144f6..23649fb0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -17,7 +17,7 @@ import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.applicationadmin.order.service.OrderService; import com.nowait.common.api.ApiUtils; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; +import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; @@ -32,51 +32,51 @@ @RequiredArgsConstructor public class OrderController { - private final OrderService orderService; + private final OrderService orderService; - @GetMapping("/{storeId}") - @Operation(summary = "주점별 주문리스트 조회", description = "특정 주점에 대한 예약리스트 조회") - @ApiResponse(responseCode = "200", description = "주리스트 조회") - public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId, - @AuthenticationPrincipal MemberDetails memberDetails) { - List response = orderService.findAllOrders(storeId,memberDetails); - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - response - ) - ); - } + @GetMapping("/{storeId}") + @Operation(summary = "주점별 주문리스트 조회", description = "특정 주점에 대한 예약리스트 조회") + @ApiResponse(responseCode = "200", description = "주리스트 조회") + public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId, + @AuthenticationPrincipal MemberDetails memberDetails) { + List response = orderService.findAllOrders(storeId, memberDetails); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + ); + } - @PatchMapping("/status/{orderId}") - @Operation(summary = "주문 상태 변경", description = "특정 주문의 상태를 변경.") - @ApiResponse(responseCode = "200", description = "주문 상태 변경 성공") - @ApiResponse(responseCode = "400", description = "주문을 찾을 수 없음") - public ResponseEntity updateOrderStatus( - @PathVariable Long orderId, - @RequestBody@Valid OrderStatusUpdateRequestDto requestDto, - @AuthenticationPrincipal MemberDetails memberDetails - ) { - OrderStatusUpdateResponseDto response = orderService.updateOrderStatus( - orderId,requestDto.getOrderStatus(),memberDetails); - return ResponseEntity - .status(HttpStatus.OK) - .body(ApiUtils.success(response)); - } + @PatchMapping("/status/{orderId}") + @Operation(summary = "주문 상태 변경", description = "특정 주문의 상태를 변경.") + @ApiResponse(responseCode = "200", description = "주문 상태 변경 성공") + @ApiResponse(responseCode = "400", description = "주문을 찾을 수 없음") + public ResponseEntity updateOrderStatus( + @PathVariable Long orderId, + @RequestBody @Valid OrderStatusUpdateRequestDto requestDto, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + OrderStatusUpdateResponseDto response = orderService.updateOrderStatus( + orderId, requestDto.getOrderStatus(), memberDetails); + return ResponseEntity + .status(HttpStatus.OK) + .body(ApiUtils.success(response)); + } - @GetMapping("/sales") - @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") - @ApiResponse(responseCode = "200", description = "오늘의 매출 조회 성공") - public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails memberDetails) { - OrderSalesSumResponse sales = orderService.getSaleSumByStoreId(memberDetails); + @GetMapping("/sales") + @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") + @ApiResponse(responseCode = "200", description = "오늘의 매출 조회 성공") + public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails memberDetails) { + OrderSalesSumDetail sales = orderService.getSaleSumByStoreId(memberDetails); - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - sales - ) - ); - } + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + sales + ) + ); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 0e23034e..3065b77e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -9,7 +9,7 @@ import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; +import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; @@ -34,13 +34,13 @@ public class OrderService { @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - storeRepository.findByStoreIdAndDeletedFalse(storeId) - .orElseThrow(StoreNotFoundException::new); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + storeRepository.findByStoreIdAndDeletedFalse(storeId).orElseThrow(StoreNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new OrderViewUnauthorizedException(); } - return orderRepository.findAllByStore_StoreId(storeId).stream() + return orderRepository.findAllByStore_StoreId(storeId) + .stream() .map(OrderResponseDto::fromEntity) .collect(Collectors.toList()); } @@ -48,9 +48,8 @@ public List findAllOrders(Long storeId, MemberDetails memberDe @Transactional public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - UserOrder userOrder = orderRepository.findById(orderId) - .orElseThrow(OrderNotFoundException::new); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { throw new OrderUpdateUnauthorizedException(); } @@ -59,8 +58,8 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus } @Transactional(readOnly = true) - public OrderSalesSumResponse getSaleSumByStoreId(MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java similarity index 69% rename from nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java index 5dc279e8..ddbff760 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumResponse.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java @@ -5,13 +5,14 @@ @Getter @Builder -public class OrderSalesSumResponse { +public class OrderSalesSumDetail { Long storeId; Integer todaySalesSum; Integer yesterdaySalesSum; Integer previousDaySales; - public OrderSalesSumResponse(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, Integer previousDaySales) { + public OrderSalesSumDetail(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, + Integer previousDaySales) { this.storeId = storeId; this.todaySalesSum = todaySalesSum; this.yesterdaySalesSum = yesterdaySalesSum; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java index c02d1a77..b3f79a1c 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java @@ -1,8 +1,8 @@ package com.nowait.domaincorerdb.order.repository; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; +import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; public interface OrderCustomRepository { - OrderSalesSumResponse findSalesSumByStoreId(Long storeId); + OrderSalesSumDetail findSalesSumByStoreId(Long storeId); } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java index c828a64e..12ccb2ee 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java @@ -5,7 +5,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumResponse; +import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; import com.nowait.domaincorerdb.order.entity.QUserOrder; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -20,7 +20,7 @@ public OrderCustomRepositoryImpl(JPAQueryFactory queryFactory) { QUserOrder u = QUserOrder.userOrder; @Override - public OrderSalesSumResponse findSalesSumByStoreId(Long storeId) { + public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { // 1. 날짜 기준 설정 (시작은 자정, 끝은 다음 날 자정) LocalDate today = LocalDate.now(); LocalDate yesterday = today.minusDays(1); @@ -71,6 +71,6 @@ public OrderSalesSumResponse findSalesSumByStoreId(Long storeId) { if (previousDaySales == null) previousDaySales = 0; // 4. 응답 객체 생성 - return new OrderSalesSumResponse(storeId, todaySum, yesterdaySum, previousDaySales); + return new OrderSalesSumDetail(storeId, todaySum, yesterdaySum, previousDaySales); } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index ff051a83..54c2b7b6 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -7,10 +7,12 @@ import org.springframework.stereotype.Repository; import com.nowait.domaincorerdb.order.entity.UserOrder; + @Repository -public interface OrderRepository extends JpaRepository, OrderCustomRepository { +public interface OrderRepository extends JpaRepository, OrderCustomRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); + List findAllByStore_StoreId(Long storeId); } From 7cbf0d0b2b05f1aa213b7962534de8f9bc474c5a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 17:35:35 +0900 Subject: [PATCH 180/685] =?UTF-8?q?feat(UserOrder):=20=EB=A7=A4=EC=B6=9C?= =?UTF-8?q?=20=EC=88=9C=EC=9C=84=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 14 ++ .../order/service/OrderService.java | 13 ++ .../order/dto/TopSalesStoresDetail.java | 25 +++ .../repository/OrderCustomRepository.java | 5 + .../repository/OrderCustomRepositoryImpl.java | 186 +++++++++++++++++- 5 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 23649fb0..0826eb47 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -18,6 +18,7 @@ import com.nowait.applicationadmin.order.service.OrderService; import com.nowait.common.api.ApiUtils; import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; +import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; @@ -79,4 +80,17 @@ public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails me ) ); } + + @GetMapping("/top-sales") + public ResponseEntity getTopSalesStores(@AuthenticationPrincipal MemberDetails memberDetails) { + List topSalesStoresDetail = orderService.getTop5StoresBySalesToday(memberDetails); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + topSalesStoresDetail + ) + ); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 3065b77e..c0d3db14 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -10,6 +10,7 @@ import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; +import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; @@ -68,4 +69,16 @@ public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails) { return orderRepository.findSalesSumByStoreId(storeId); } + + @Transactional(readOnly = true) + public List getTop5StoresBySalesToday(MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long storeId = user.getStoreId(); + + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new OrderViewUnauthorizedException(); + } + + return orderRepository.getTop4PlusMine(storeId); + } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java new file mode 100644 index 00000000..acc0d2d3 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java @@ -0,0 +1,25 @@ +package com.nowait.domaincorerdb.order.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class TopSalesStoresDetail { + private Long storeId; + private String storeName; + private Long departmentId; + private String departmentName; + private Integer totalSales; + private Long storeRank; + + public TopSalesStoresDetail(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales, + Long storeRank) { + this.storeId = storeId; + this.storeName = storeName; + this.departmentId = departmentId; + this.departmentName = departmentName; + this.totalSales = totalSales; + this.storeRank = storeRank; + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java index b3f79a1c..77d2747d 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java @@ -1,8 +1,13 @@ package com.nowait.domaincorerdb.order.repository; +import java.util.List; + import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; +import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; public interface OrderCustomRepository { OrderSalesSumDetail findSalesSumByStoreId(Long storeId); + + List getTop4PlusMine(Long storeId); } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java index 12ccb2ee..c2c2d1c9 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java @@ -4,9 +4,20 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import com.nowait.domaincorerdb.department.entity.QDepartment; import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; +import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.order.entity.QUserOrder; +import com.nowait.domaincorerdb.store.entity.QStore; +import com.querydsl.core.Tuple; import com.querydsl.jpa.impl.JPAQueryFactory; public class OrderCustomRepositoryImpl implements OrderCustomRepository { @@ -18,6 +29,8 @@ public OrderCustomRepositoryImpl(JPAQueryFactory queryFactory) { } QUserOrder u = QUserOrder.userOrder; + QStore s = QStore.store; + QDepartment d = QDepartment.department; @Override public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { @@ -66,11 +79,178 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { .fetchOne(); // null 방어 처리 - if (todaySum == null) todaySum = 0; - if (yesterdaySum == null) yesterdaySum = 0; - if (previousDaySales == null) previousDaySales = 0; + if (todaySum == null) + todaySum = 0; + if (yesterdaySum == null) + yesterdaySum = 0; + if (previousDaySales == null) + previousDaySales = 0; // 4. 응답 객체 생성 return new OrderSalesSumDetail(storeId, todaySum, yesterdaySum, previousDaySales); } + + @Override + public List getTop4PlusMine(Long userStoreId) { + LocalDate today = LocalDate.now(); + LocalDateTime todayStart = today.atStartOfDay(); + LocalDateTime todayEnd = today.plusDays(1).atStartOfDay(); // 내일 00:00:00 + + // 1. 전체 주점 및 순위 맵 + List allStores = getAllStores(todayStart, todayEnd); + Map rankMap = getRankMap(allStores); + + // 2. 내 주점의 순위가 5위 이하인지 확인 + Integer userRank = rankMap.get(userStoreId); + + // 3. 상위 5개 주점 조회 (내 주점 포함 여부는 나중에 확인) + List result = getTopNStores(allStores, rankMap, 5); + Set already = result.stream() + .map(TopSalesStoresDetail::getStoreId) + .collect(Collectors.toSet()); + + // 4. 내 주점 추가 (내 주점이 포함되지 않으면 추가) + TopSalesStoresDetail myStore = getMyStoreDetail(allStores, rankMap, userStoreId); + if (myStore == null) + myStore = getZeroSalesStore(userStoreId); + + if (myStore != null && !already.contains(userStoreId)) { + // 내 주점이 5위 이하로 밀린 경우 + if (userRank > 5) { + result = result.stream() + .sorted(Comparator.comparing(TopSalesStoresDetail::getStoreRank, + Comparator.nullsLast(Comparator.naturalOrder()))) + .limit(4) + .collect(Collectors.toList()); + // 5위 자리에 내 주점 추가하고 나머지 4개만 보여줌 + result.add(myStore); + } + } + + // 5. 결과가 6개가 되지 않도록 자르고 5개로 제한 + result = result.stream() + .sorted(Comparator.comparing(TopSalesStoresDetail::getStoreRank, + Comparator.nullsLast(Comparator.naturalOrder()))) + .collect(Collectors.toList()); + + return result; + } + + private List getAllStores(LocalDateTime todayStart, LocalDateTime todayEnd) { + return queryFactory + .select(u.store.storeId, u.store.name, u.store.departmentId, u.totalPrice.sum()) + .from(u) + .where( + u.createdAt.goe(todayStart), + u.createdAt.lt(todayEnd), + u.status.eq(COOKED) + ) + .groupBy(u.store.storeId, u.store.name, u.store.departmentId) + .orderBy(u.totalPrice.sum().desc()) + .fetch(); + } + + private Map getRankMap(List allStores) { + Map rankMap = new HashMap<>(); + int rank = 1; + for (Tuple t : allStores) { + rankMap.put(t.get(u.store.storeId), rank++); + } + return rankMap; + } + + private List getTopNStores(List allStores, Map rankMap, int n) { + // 1. departmentId 목록을 추출 + Set departmentIds = allStores.stream() + .map(t -> t.get(u.store.departmentId)) + .collect(Collectors.toSet()); + + // 2. departmentId에 대한 학과 이름을 한번에 가져오기 + Map departmentNameMap = getDepartmentNames(departmentIds); + + List list = new ArrayList<>(); + for (int i = 0; i < Math.min(n, allStores.size()); i++) { + Tuple t = allStores.get(i); + Long storeId = t.get(u.store.storeId); + String storeName = t.get(u.store.name); + Long departmentId = t.get(u.store.departmentId); + + // 학과 이름 가져오기 + String departmentName = departmentNameMap.getOrDefault(departmentId, "Unknown"); + + list.add(new TopSalesStoresDetail( + storeId, + storeName, + t.get(u.store.departmentId), + departmentName, + t.get(u.totalPrice.sum()), + rankMap.get(storeId).longValue() + )); + } + return list; + } + + private TopSalesStoresDetail getMyStoreDetail(List allStores, Map rankMap, Long userStoreId) { + Tuple myStore = allStores.stream() + .filter(t -> t.get(u.store.storeId).equals(userStoreId)) + .findFirst() + .orElse(null); + + Long departmentId = myStore.get(u.store.departmentId); + + // departmentId에 대한 학과 이름을 한번에 가져오기 + Map departmentNameMap = getDepartmentNames(Set.of(departmentId)); + String departmentName = departmentNameMap.getOrDefault(departmentId, "Unknown"); + + if (myStore == null) + return null; + Long storeId = myStore.get(u.store.storeId); + return new TopSalesStoresDetail( + storeId, + myStore.get(u.store.name), + myStore.get(u.store.departmentId), + departmentName, + myStore.get(u.totalPrice.sum()), + rankMap.get(storeId).longValue() + ); + } + + private TopSalesStoresDetail getZeroSalesStore(Long userStoreId) { + Tuple zeroTuple = queryFactory + .select(s.storeId, s.name, s.departmentId) + .from(s) + .where(s.storeId.eq(userStoreId)) + .fetchOne(); + + Long departmentId = zeroTuple.get(u.store.departmentId); + // departmentId에 대한 학과 이름을 한번에 가져오기 + Map departmentNameMap = getDepartmentNames(Set.of(departmentId)); + String departmentName = departmentNameMap.getOrDefault(departmentId, "Unknown"); + + if (zeroTuple == null) + return null; + return new TopSalesStoresDetail( + zeroTuple.get(s.storeId), + zeroTuple.get(s.name), + zeroTuple.get(s.departmentId), + departmentName, + 0, + null + ); + } + + // 한 번에 학과 이름을 가져오는 메서드 + private Map getDepartmentNames(Set departmentIds) { + List departmentTuples = queryFactory + .select(d.Id, d.name) + .from(d) + .where(d.Id.in(departmentIds)) + .fetch(); + + Map departmentNameMap = new HashMap<>(); + for (Tuple t : departmentTuples) { + departmentNameMap.put(t.get(d.Id), t.get(d.name)); + } + return departmentNameMap; + } } From 66e4f3fb41c4ca01df6bb538c677947c212e8d80 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 17:36:02 +0900 Subject: [PATCH 181/685] =?UTF-8?q?feat(Department):=20=ED=95=99=EA=B3=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=9C=84=ED=95=9C=20departmen?= =?UTF-8?q?t=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../department/entity/Department.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java new file mode 100644 index 00000000..59838165 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java @@ -0,0 +1,24 @@ +package com.nowait.domaincorerdb.department.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "departments") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class Department { + + @Id + private Long Id; + + @Column(nullable = false) + private String name; +} From 20ffe4bf106bf5f78ed82540145c92fbcf4c22bc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 19:58:24 +0900 Subject: [PATCH 182/685] =?UTF-8?q?feat:=20Redis=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-domain/domain-redis/build.gradle | 36 +++++++++++++++++ .../domaincoreredis/config/RedisConfig.java | 40 +++++++++++++++++++ .../service/StoreRankCacheService.java | 10 +++++ settings.gradle | 1 + 4 files changed, 87 insertions(+) create mode 100644 nowait-domain/domain-redis/build.gradle create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java diff --git a/nowait-domain/domain-redis/build.gradle b/nowait-domain/domain-redis/build.gradle new file mode 100644 index 00000000..8a6470e6 --- /dev/null +++ b/nowait-domain/domain-redis/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'java-library' +} + +jar { + enabled = true +} + + +group = 'com.nowait' +version = rootProject.version + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':nowait-common') + + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + + compileOnly 'org.projectlombok:lombok:1.18.26' + annotationProcessor 'org.projectlombok:lombok:1.18.26' + + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java new file mode 100644 index 00000000..57e8b919 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java @@ -0,0 +1,40 @@ +package com.nowait.domaincoreredis.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new StringRedisSerializer()); + + redisTemplate.setDefaultSerializer(new StringRedisSerializer()); + + return redisTemplate; + } +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java new file mode 100644 index 00000000..444aedcf --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java @@ -0,0 +1,10 @@ +package com.nowait.domaincoreredis.service; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +public class StoreRankCacheService { + + private RedisTemplate redisTemplate; +} diff --git a/settings.gradle b/settings.gradle index 24f20718..89991c90 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,4 +6,5 @@ include 'nowait-common' include 'nowait-domain:domain-core-rdb' include 'nowait-domain:domain-admin-rdb' include 'nowait-domain:domain-user-rdb' +include 'nowait-domain:domain-redis' include 'nowait-infra' From fdc23dce470d53628a6ba82c45fb235b91a47001 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 20:16:48 +0900 Subject: [PATCH 183/685] =?UTF-8?q?refactor(Department):=20Java=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=B3=80=EC=88=98=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincorerdb/department/entity/Department.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java index 59838165..51c54577 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/entity/Department.java @@ -17,7 +17,7 @@ public class Department { @Id - private Long Id; + private Long id; @Column(nullable = false) private String name; From 4707bf5c475e36d4be94e348d51c637fd9cea267 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 20:17:55 +0900 Subject: [PATCH 184/685] =?UTF-8?q?refactor:=20private=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EC=A0=9C=ED=95=9C=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/order/dto/OrderSalesSumDetail.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java index ddbff760..a3688ab2 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java @@ -6,10 +6,10 @@ @Getter @Builder public class OrderSalesSumDetail { - Long storeId; - Integer todaySalesSum; - Integer yesterdaySalesSum; - Integer previousDaySales; + private Long storeId; + private Integer todaySalesSum; + private Integer yesterdaySalesSum; + private Integer previousDaySales; public OrderSalesSumDetail(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, Integer previousDaySales) { From 125ffc26acabe520ea6e8fd79622e8f2be2e9d9f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 20:18:40 +0900 Subject: [PATCH 185/685] =?UTF-8?q?refactor(UserOrder):=20=EC=8A=A4?= =?UTF-8?q?=EC=9B=A8=EA=B1=B0=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/controller/OrderController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 0826eb47..16246fbc 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -82,6 +82,8 @@ public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails me } @GetMapping("/top-sales") + @Operation(summary = "오늘의 매출 상위 5개 주점 조회", description = "오늘의 매출이 가장 높은 상위 5개 주점을 조회합니다.") + @ApiResponse(responseCode = "200", description = "오늘의 매출 상위 5개 주점 조회 성공") public ResponseEntity getTopSalesStores(@AuthenticationPrincipal MemberDetails memberDetails) { List topSalesStoresDetail = orderService.getTop5StoresBySalesToday(memberDetails); From 1ec1a60e03f6c1b73aff4e830ad9c67d4aeb1613 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 20:20:32 +0900 Subject: [PATCH 186/685] =?UTF-8?q?refactor(UserOrder):=20null=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EC=A0=84=EC=97=90=20zeroTuple=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20NullPointerException=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/repository/OrderCustomRepositoryImpl.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java index c2c2d1c9..18b7e5eb 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java @@ -28,9 +28,9 @@ public OrderCustomRepositoryImpl(JPAQueryFactory queryFactory) { this.queryFactory = queryFactory; } - QUserOrder u = QUserOrder.userOrder; - QStore s = QStore.store; - QDepartment d = QDepartment.department; + private static final QUserOrder u = QUserOrder.userOrder; + private static final QStore s = QStore.store; + private static final QDepartment d = QDepartment.department; @Override public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { @@ -222,13 +222,14 @@ private TopSalesStoresDetail getZeroSalesStore(Long userStoreId) { .where(s.storeId.eq(userStoreId)) .fetchOne(); + if (zeroTuple == null) + return null; + Long departmentId = zeroTuple.get(u.store.departmentId); // departmentId에 대한 학과 이름을 한번에 가져오기 Map departmentNameMap = getDepartmentNames(Set.of(departmentId)); String departmentName = departmentNameMap.getOrDefault(departmentId, "Unknown"); - if (zeroTuple == null) - return null; return new TopSalesStoresDetail( zeroTuple.get(s.storeId), zeroTuple.get(s.name), From 82616029e03c9596422f879c6774b96e32070ae8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 20:23:42 +0900 Subject: [PATCH 187/685] =?UTF-8?q?refactor(UserOrder):=20=EB=88=84?= =?UTF-8?q?=EC=A0=81=EB=A7=A4=EC=B6=9C=EC=97=90=20=EB=A7=9E=EB=8A=94=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/dto/OrderSalesSumDetail.java | 6 +++--- .../repository/OrderCustomRepositoryImpl.java | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java index a3688ab2..fe3b25ee 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java @@ -9,13 +9,13 @@ public class OrderSalesSumDetail { private Long storeId; private Integer todaySalesSum; private Integer yesterdaySalesSum; - private Integer previousDaySales; + private Integer cumulativeSalesBeforeYesterday; public OrderSalesSumDetail(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, - Integer previousDaySales) { + Integer cumulativeSalesBeforeYesterday) { this.storeId = storeId; this.todaySalesSum = todaySalesSum; this.yesterdaySalesSum = yesterdaySalesSum; - this.previousDaySales = previousDaySales; + this.cumulativeSalesBeforeYesterday = cumulativeSalesBeforeYesterday; } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java index 18b7e5eb..d97167a9 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java @@ -68,7 +68,7 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { ) .fetchOne(); - Integer previousDaySales = queryFactory + Integer cumulativeSalesBeforeYesterday = queryFactory .select(u.totalPrice.sum()) .from(u) .where( @@ -83,11 +83,11 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { todaySum = 0; if (yesterdaySum == null) yesterdaySum = 0; - if (previousDaySales == null) - previousDaySales = 0; + if (cumulativeSalesBeforeYesterday == null) + cumulativeSalesBeforeYesterday = 0; // 4. 응답 객체 생성 - return new OrderSalesSumDetail(storeId, todaySum, yesterdaySum, previousDaySales); + return new OrderSalesSumDetail(storeId, todaySum, yesterdaySum, cumulativeSalesBeforeYesterday); } @Override @@ -243,14 +243,14 @@ private TopSalesStoresDetail getZeroSalesStore(Long userStoreId) { // 한 번에 학과 이름을 가져오는 메서드 private Map getDepartmentNames(Set departmentIds) { List departmentTuples = queryFactory - .select(d.Id, d.name) + .select(d.id, d.name) .from(d) - .where(d.Id.in(departmentIds)) + .where(d.id.in(departmentIds)) .fetch(); Map departmentNameMap = new HashMap<>(); for (Tuple t : departmentTuples) { - departmentNameMap.put(t.get(d.Id), t.get(d.name)); + departmentNameMap.put(t.get(d.id), t.get(d.name)); } return departmentNameMap; } From e7455508fd3b82148ce4c91ae49b8883cf20b95f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 20:24:49 +0900 Subject: [PATCH 188/685] =?UTF-8?q?refactor(UserOrder):=20null=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EC=A0=84=EC=97=90=20myStore=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20NullPointerException=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/repository/OrderCustomRepositoryImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java index d97167a9..341940bb 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java @@ -196,14 +196,15 @@ private TopSalesStoresDetail getMyStoreDetail(List allStores, Map departmentNameMap = getDepartmentNames(Set.of(departmentId)); String departmentName = departmentNameMap.getOrDefault(departmentId, "Unknown"); - if (myStore == null) - return null; Long storeId = myStore.get(u.store.storeId); return new TopSalesStoresDetail( storeId, From 8438788de6461b1124eb72a27ce33bd5cdaec5aa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 15 Jul 2025 21:16:26 +0900 Subject: [PATCH 189/685] =?UTF-8?q?doc:=20=EC=9D=B4=EC=8A=88=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27\224\353\223\234-\354\235\264\354\212\210.md" | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ".github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" diff --git "a/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" new file mode 100644 index 00000000..f728915f --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\353\260\261\354\227\224\353\223\234-\354\235\264\354\212\210.md" @@ -0,0 +1,14 @@ +--- +name: 백엔드 이슈 +about: 백엔드와 관련된 이슈 +title: "[백엔드] " +labels: Backend +assignees: '' + +--- + +# 이슈 내용 + + +# 작업 목록 +- [ ] From 029e5aaf38b16a33fe5ec64f463cb00857f3e63b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:35:59 +0900 Subject: [PATCH 190/685] =?UTF-8?q?feat:=20Redis=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index f6f6a509..028c8818 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -21,6 +21,7 @@ dependencies { implementation project(':nowait-infra') implementation project(':nowait-domain:domain-admin-rdb') implementation project(':nowait-domain:domain-core-rdb') + implementation project(':nowait-domain:domain-redis') // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' @@ -36,6 +37,9 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // SWAGGER implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' From 83d543b8b07fcf73d43b819a3059c50a037601c7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:36:13 +0900 Subject: [PATCH 191/685] =?UTF-8?q?feat:=20QueryDSL=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9D=98=20=EC=9C=84=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-domain/domain-admin-rdb/build.gradle | 21 ++++++++++++++++++--- nowait-domain/domain-core-rdb/build.gradle | 11 ++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/nowait-domain/domain-admin-rdb/build.gradle b/nowait-domain/domain-admin-rdb/build.gradle index bf111954..21a07f1f 100644 --- a/nowait-domain/domain-admin-rdb/build.gradle +++ b/nowait-domain/domain-admin-rdb/build.gradle @@ -27,12 +27,27 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-data-jpa' api 'jakarta.persistence:jakarta.persistence-api:3.1.0' + // SPRING SECURITY + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-validation' + + //QueryDsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' + // Jackson + api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' + testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' } diff --git a/nowait-domain/domain-core-rdb/build.gradle b/nowait-domain/domain-core-rdb/build.gradle index daa8ad3d..48bcad51 100644 --- a/nowait-domain/domain-core-rdb/build.gradle +++ b/nowait-domain/domain-core-rdb/build.gradle @@ -31,11 +31,6 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-validation' - //QueryDsl - implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" // SWAGGER implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' @@ -43,6 +38,12 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' + //QueryDsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // Jackson api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' From 2e547cbcf2e9841305b5c9c520a7297f565d3311 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:36:36 +0900 Subject: [PATCH 192/685] =?UTF-8?q?feat(Statistics):=20Statistics=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/common/exception/ErrorMessage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index b64c5e37..fa83423b 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -50,7 +50,6 @@ public enum ErrorMessage { STORE_WAITING_DISABLED("해당 주점은 대기 비활성화된 주점입니다.", "store006"), // storePayment - STORE_PAYMENT_PARAMETER_EMPTY("주점 결제 생성 시 파라미터 정보가 없습니다.", "storePayment001"), STORE_PAYMENT_NOT_FOUND("해당 주점 결제 정보를 찾을 수 없습니다.", "storePayment002"), STORE_PAYMENT_VIEW_UNAUTHORIZED("주점 결제 정보 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment003"), @@ -59,6 +58,8 @@ public enum ErrorMessage { STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment006"), + // Statistics + STATISTIC_VIEW_UNAUTHORIZED("통계 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "statistics001"), // image IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), From 502d1e322eea23ec04686129c56a8f55501bca92 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:42:43 +0900 Subject: [PATCH 193/685] =?UTF-8?q?feat(Statistics):=20=EB=9E=AD=ED=82=B9?= =?UTF-8?q?=20repository=20Redis=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EC=A1=B0=ED=9A=8C/=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/RankingQueryRepository.java | 16 +++++ .../RankingQueryRepositoryImpl.java | 65 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepository.java create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepository.java new file mode 100644 index 00000000..f056b146 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepository.java @@ -0,0 +1,16 @@ +package com.nowait.domaincoreredis.rank.repository; + +import java.util.List; + +import com.nowait.domaincoreredis.rank.dto.RankingEntry; + +public interface RankingQueryRepository { + + void addToRanking(String key, Long storeId, Integer totalSales); + + Long findPrevRank(String key, Long storeId); + + List findTopStores(String key, int topN); + + List findTopStoresWithUser(String key, Long userStoreId, int topN, List entries); +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java new file mode 100644 index 00000000..9fcc721d --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java @@ -0,0 +1,65 @@ +package com.nowait.domaincoreredis.rank.repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import com.nowait.domaincoreredis.rank.dto.RankingEntry; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class RankingQueryRepositoryImpl implements RankingQueryRepository { + + private final StringRedisTemplate redisTemplate; + + @Override + public void addToRanking(String key, Long storeId, Integer totalSales) { + double totalSalesDouble = totalSales != null ? totalSales.doubleValue() : 0.0; + redisTemplate.opsForZSet().add(key, String.valueOf(storeId), totalSalesDouble); + } + + @Override + public Long findPrevRank(String key, Long storeId) { + return redisTemplate.opsForZSet().reverseRank(key, String.valueOf(storeId)); + } + + @Override + public List findTopStores(String key, int topN) { + var topTuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, topN - 1); + + List entries = topTuples.stream() + .map(tuple -> new RankingEntry( + Long.parseLong(tuple.getValue()), + tuple.getScore().intValue(), + redisTemplate.opsForZSet().reverseRank(key, tuple.getValue()) + 1, + 0 + )) + .collect(Collectors.toCollection(ArrayList::new)); + + return entries; + } + + @Override + public List findTopStoresWithUser(String key, Long userStoreId, int topN, List entries) { + Long userZero = redisTemplate.opsForZSet().reverseRank(key, userStoreId.toString()); + + if (userZero != null && userZero >= topN) { + List top4 = entries.subList(0, topN - 1); + Integer userSales = redisTemplate.opsForZSet().score(key, userStoreId.toString()).intValue(); + + top4.add(new RankingEntry( + userStoreId, + userSales, + userZero + 1, + 0 // 초기 등락 값은 0으로 설정 + )); + entries = top4; + } + return entries; + } +} From 97169dd3bfba7831fdf5cd1add31559680443a7f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:43:13 +0900 Subject: [PATCH 194/685] =?UTF-8?q?feat(Statistics):=20=EB=9E=AD=ED=82=B9?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EB=93=B1=EB=9D=BD=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rank/service/RankingQueryService.java | 13 ++++++ .../rank/service/RankingQueryServiceImpl.java | 44 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryService.java create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryService.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryService.java new file mode 100644 index 00000000..5b0eb5b5 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryService.java @@ -0,0 +1,13 @@ +package com.nowait.domaincoreredis.rank.service; + +import java.util.List; + +import com.nowait.domaincoreredis.rank.dto.RankingEntry; + +public interface RankingQueryService { + + /** + * 로그인한 사용자의 주점을 포함하여 상위 topN 순위와 등락 정보를 반환한다. + */ + List getRankings(Long userStoreId, int topN); +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java new file mode 100644 index 00000000..d3bc0050 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java @@ -0,0 +1,44 @@ +package com.nowait.domaincoreredis.rank.service; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.rank.dto.RankingEntry; +import com.nowait.domaincoreredis.rank.repository.RankingQueryRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class RankingQueryServiceImpl implements RankingQueryService { + + private final RankingQueryRepository rankingQueryRepository; + + @Override + public List getRankings(Long userStoreId, int topN) { + // 1) 현재 스냅샷에서 상위 topN 주점 조회 + List entries = rankingQueryRepository.findTopStores(RedisKeyUtils.buildCurrentKey(), topN); + + // 2) 사용자 주점이 topN 밖이면 4위까지 + 사용자 주점 + entries = rankingQueryRepository.findTopStoresWithUser(RedisKeyUtils.buildCurrentKey(), userStoreId, topN, entries); + + // 3) 이전 스냅샷에서 등락 정보 조회 + for (int i = 0; i < entries.size(); i++) { + var entry = entries.get(i); + Long prevZero = rankingQueryRepository.findPrevRank(RedisKeyUtils.buildPreviousKey(), entry.getStoreId()); + long prevRank = (prevZero == null ? entry.getCurrentRank() : prevZero + 1); + int delta = (int)(prevRank - entry.getCurrentRank()); + + entries.set(i, new RankingEntry( + entry.getStoreId(), + entry.getTotalSales(), + entry.getCurrentRank(), + delta + )); + } + + return entries; + } +} From 2518c63a2a27dcfdfc2b712494df08a41a1dffa4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:43:38 +0900 Subject: [PATCH 195/685] =?UTF-8?q?feat(Statistics):=20=EB=9E=AD=ED=82=B9?= =?UTF-8?q?=20=ED=86=B5=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20Redis-DB=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/service/RankingService.java | 10 +++ .../service/impl/RankingServiceImpl.java | 75 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/RankingService.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/RankingService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/RankingService.java new file mode 100644 index 00000000..b34f2f8b --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/RankingService.java @@ -0,0 +1,10 @@ +package com.nowait.applicationadmin.statistic.service; + +import java.util.List; + +import com.nowait.applicationadmin.statistic.dto.StoreRankingDto; +import com.nowait.domaincorerdb.user.entity.MemberDetails; + +public interface RankingService { + List getStatisticsRankings(MemberDetails memberDetails); +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java new file mode 100644 index 00000000..9b5d764b --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java @@ -0,0 +1,75 @@ +package com.nowait.applicationadmin.statistic.service.impl; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationadmin.statistic.dto.StoreRankingDto; +import com.nowait.applicationadmin.statistic.service.RankingService; +import com.nowait.common.enums.Role; +import com.nowait.domainadminrdb.statistic.dto.StoreInfo; +import com.nowait.domainadminrdb.statistic.exception.StatisticViewUnauthorizedException; +import com.nowait.domainadminrdb.statistic.repository.StatisticCustomRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.rank.dto.RankingEntry; +import com.nowait.domaincoreredis.rank.service.RankingQueryService; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class RankingServiceImpl implements RankingService { + + private final StatisticCustomRepository statisticCustomRepository; + private final UserRepository userRepository; + private final RankingQueryService rankingQuery; + + + @Override + @Transactional(readOnly = true) + public List getStatisticsRankings(MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long userStoreId = user.getStoreId(); + + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userStoreId)) { + throw new StatisticViewUnauthorizedException(); + } + + // 1) Redis에서 Top4+내주점: storeId, totalSales, currentRank, delta + List entries = rankingQuery.getRankings(userStoreId, 5); + + // 2) DB에서 store 정보 가져오기 + List storeIds = entries.stream() + .map(RankingEntry::getStoreId) + .toList(); + + List infos = statisticCustomRepository.findStoreInfoByIds(storeIds); + + // StoreInfo를 storeId로 매핑 + Map infoMap = infos.stream() + .collect(Collectors.toMap(StoreInfo::getStoreId, Function.identity())); + + // 3) 매핑 → 최종 DTO + return entries.stream() + .map(e -> { + StoreInfo info = infoMap.get(e.getStoreId()); + return new StoreRankingDto( + e.getStoreId(), + info.getStoreName(), + info.getDepartmentId(), + info.getDepartmentName(), + e.getTotalSales(), + e.getCurrentRank(), + e.getDelta() + ); + }) + .collect(Collectors.toList()); + } +} From 465396ff2de8ea567a51d03eb90b32af9e6a37fa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:44:24 +0900 Subject: [PATCH 196/685] =?UTF-8?q?feat(Statistics):=20=ED=86=B5=EA=B3=84?= =?UTF-8?q?=20=EC=BB=A4=EC=8A=A4=ED=85=80=20repository=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EB=A7=A4=EC=B6=9C/=EB=9E=AD=ED=82=B9?= =?UTF-8?q?=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/StatisticCustomRepository.java | 23 +++++++ .../StatisticCustomRepositoryImpl.java} | 66 +++++++++++++++++-- 2 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java rename nowait-domain/{domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java => domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java} (81%) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java new file mode 100644 index 00000000..8c03bcdd --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java @@ -0,0 +1,23 @@ +package com.nowait.domainadminrdb.statistic.repository; + +import java.util.List; + +import com.nowait.domainadminrdb.statistic.dto.OrderSalesSumDetail; +import com.nowait.domainadminrdb.statistic.dto.StoreInfo; +import com.nowait.domainadminrdb.statistic.dto.StoreSales; +import com.nowait.domainadminrdb.statistic.dto.TopSalesStoresDetail; + + + +public interface StatisticCustomRepository { + + OrderSalesSumDetail findSalesSumByStoreId(Long storeId); + + List getTop4PlusMine(Long storeId); + + + // redis 사용하는 부분 + List findTotalSales(); + + List findStoreInfoByIds(List storeIds); +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java similarity index 81% rename from nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java rename to nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java index 341940bb..440c13a6 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepositoryImpl.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java @@ -1,6 +1,7 @@ -package com.nowait.domaincorerdb.order.repository; +package com.nowait.domainadminrdb.statistic.repository; import static com.nowait.domaincorerdb.order.entity.OrderStatus.*; +import static com.nowait.domaincorerdb.store.entity.QStore.*; import java.time.LocalDate; import java.time.LocalDateTime; @@ -12,24 +13,29 @@ import java.util.Set; import java.util.stream.Collectors; +import org.springframework.stereotype.Repository; + +import com.nowait.domainadminrdb.statistic.dto.OrderSalesSumDetail; +import com.nowait.domainadminrdb.statistic.dto.StoreInfo; +import com.nowait.domainadminrdb.statistic.dto.StoreSales; +import com.nowait.domainadminrdb.statistic.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.department.entity.QDepartment; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; -import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.order.entity.QUserOrder; import com.nowait.domaincorerdb.store.entity.QStore; import com.querydsl.core.Tuple; import com.querydsl.jpa.impl.JPAQueryFactory; -public class OrderCustomRepositoryImpl implements OrderCustomRepository { +@Repository +public class StatisticCustomRepositoryImpl implements StatisticCustomRepository { private final JPAQueryFactory queryFactory; - public OrderCustomRepositoryImpl(JPAQueryFactory queryFactory) { + public StatisticCustomRepositoryImpl(JPAQueryFactory queryFactory) { this.queryFactory = queryFactory; } private static final QUserOrder u = QUserOrder.userOrder; - private static final QStore s = QStore.store; + private static final QStore s = store; private static final QDepartment d = QDepartment.department; @Override @@ -255,4 +261,52 @@ private Map getDepartmentNames(Set departmentIds) { } return departmentNameMap; } + + + + // redis 사용하는 부분 + @Override + public List findTotalSales() { + // 오늘 자정 + LocalDate today = LocalDate.now(); + LocalDateTime todayStart = today.atStartOfDay(); + LocalDateTime todayEnd = today.plusDays(1).atStartOfDay(); // 내일 00:00:00 + + List rows = queryFactory + .select(u.store.storeId, u.totalPrice.sum().coalesce(0)) + .from(u) + .where( + u.createdAt.goe(todayStart), + u.createdAt.lt(todayEnd), + u.status.eq(COOKED) + ) + .groupBy(u.store.storeId) + .fetch(); + + return rows.stream() + .map(t -> new StoreSales( + t.get(u.store.storeId), + t.get(u.totalPrice.sum().coalesce(0)) + )) + .collect(Collectors.toList()); + } + + @Override + public List findStoreInfoByIds(List storeIds) { + List tuples = queryFactory + .select(s.storeId, s.name, store.departmentId, d.name) + .from(store) + .join(d).on(store.departmentId.eq(d.id)) + .where(store.storeId.in(storeIds)) + .fetch(); + + return tuples.stream() + .map(t -> new StoreInfo( + t.get(store.storeId), + t.get(store.name), + t.get(store.departmentId), + t.get(d.name) + )) + .collect(Collectors.toList()); + } } From d94b13b8c179b6670fff78c290f12625d06ca21f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:44:57 +0900 Subject: [PATCH 197/685] =?UTF-8?q?feat(Statistics):=20=ED=86=B5=EA=B3=84?= =?UTF-8?q?=20API=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EB=A7=A4=EC=B6=9C,=20=EB=9E=AD?= =?UTF-8?q?=ED=82=B9=20=EC=A1=B0=ED=9A=8C=20=EC=97=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StatisticsController.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java new file mode 100644 index 00000000..0ad9f0e2 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java @@ -0,0 +1,61 @@ +package com.nowait.applicationadmin.statistic.controller; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationadmin.order.service.OrderService; +import com.nowait.applicationadmin.statistic.dto.StoreRankingDto; +import com.nowait.applicationadmin.statistic.service.RankingService; +import com.nowait.common.api.ApiUtils; +import com.nowait.domainadminrdb.statistic.dto.OrderSalesSumDetail; +import com.nowait.domaincorerdb.user.entity.MemberDetails; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "Statistics API", description = "통계 API") +@RestController +@RequestMapping("/admin/statistics") +@RequiredArgsConstructor +public class StatisticsController { + + private final OrderService orderService; + private final RankingService rankingService; + + @GetMapping("/sales") + @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") + @ApiResponse(responseCode = "200", description = "오늘의 매출 조회 성공") + public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails memberDetails) { + OrderSalesSumDetail sales = orderService.getSaleSumByStoreId(memberDetails); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + sales + ) + ); + } + + @GetMapping("/top-sales") + @Operation(summary = "주점별 매출 통계 조회", description = "주점별 매출 통계를 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점별 매출 통계 조회 성공") + public ResponseEntity getTopSalesStores(@AuthenticationPrincipal MemberDetails memberDetails) { + List response = rankingService.getStatisticsRankings(memberDetails); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + ); + } +} From 079897ef6c61791426f69ee64b37dca1618bce7b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:45:22 +0900 Subject: [PATCH 198/685] =?UTF-8?q?feat(Statistics):=20=EB=9E=AD=ED=82=B9?= =?UTF-8?q?=20=EC=8A=A4=EB=83=85=EC=83=B7=20=EC=A3=BC=EA=B8=B0=EC=A0=81=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/RankingRefreshScheduler.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java new file mode 100644 index 00000000..bd1f47a8 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -0,0 +1,62 @@ +package com.nowait.applicationadmin.statistic.scheduler; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import com.nowait.domainadminrdb.statistic.dto.StoreSales; +import com.nowait.domainadminrdb.statistic.repository.StatisticCustomRepository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.rank.repository.RankingQueryRepository; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Configuration +@EnableScheduling +@RequiredArgsConstructor +@Slf4j +public class RankingRefreshScheduler { + + private final StatisticCustomRepository statisticCustomRepository; + private final RankingQueryRepository rankingQueryRepository; + private final RedisTemplate redis; + + @PostConstruct + public void init() { + // 초기화 작업 + refresh(); + } + + @Scheduled(cron = "0 */5 * * * *") // 매 5분마다 실행 + public void refresh() { + log.info("RankingRefreshScheduler.refresh() called at {}", LocalDateTime.now()); + + String nextKey = RedisKeyUtils.buildNextKey(); + String currentKey = RedisKeyUtils.buildCurrentKey(); + String previousKey = RedisKeyUtils.buildPreviousKey(); + + // 1) 다음 스냅샷 키 초기화 + redis.delete(nextKey); + + // 2) DB에서 매출 합계 가져와 ZADD + List salesList = statisticCustomRepository.findTotalSales(); + + salesList.forEach(s -> + rankingQueryRepository.addToRanking(nextKey, s.getStoreId(), s.getTotalSales()) + ); + + // 3) 현재 스냅샷 키를 이전 스냅샷 키로 이동 + if (redis.hasKey(currentKey)) { + redis.rename(currentKey, previousKey); + } + if (redis.hasKey(nextKey)) { + redis.rename(nextKey, currentKey); + } + } +} From 8b9c3c698b7451d7c85f82966bb68bc0e061ee18 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:45:34 +0900 Subject: [PATCH 199/685] =?UTF-8?q?feat(Statistics):=20=EB=9E=AD=ED=82=B9?= =?UTF-8?q?=20=EC=8A=A4=EB=83=85=EC=83=B7=EC=9A=A9=20Redis=20=ED=82=A4=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/util/RedisKeyUtils.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java new file mode 100644 index 00000000..fd05a3cb --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -0,0 +1,24 @@ +package com.nowait.domaincoreredis.common.util; + +public class RedisKeyUtils { + + private static final String KEY_CURRENT = "nowait:store:rank:current"; + private static final String KEY_PREVIOUS = "nowait:store:rank:previous"; + private static final String KEY_NEXT = "nowait:store:rank:next"; + + private RedisKeyUtils() { + throw new UnsupportedOperationException("유틸리티 서비스는 인스턴스화 할 수 없습니다."); + } + + public static String buildCurrentKey() { + return KEY_CURRENT; + } + + public static String buildPreviousKey() { + return KEY_PREVIOUS; + } + + public static String buildNextKey() { + return KEY_NEXT; + } +} From cb736355fb9fcdc389877aaebb15591ea934979f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:45:51 +0900 Subject: [PATCH 200/685] =?UTF-8?q?feat(Statistics):=20StatisticViewUnauth?= =?UTF-8?q?orizedException=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/StatisticViewUnauthorizedException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/exception/StatisticViewUnauthorizedException.java diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/exception/StatisticViewUnauthorizedException.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/exception/StatisticViewUnauthorizedException.java new file mode 100644 index 00000000..0757f643 --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/exception/StatisticViewUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domainadminrdb.statistic.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StatisticViewUnauthorizedException extends RuntimeException { + public StatisticViewUnauthorizedException() { + super(ErrorMessage.STATISTIC_VIEW_UNAUTHORIZED.getMessage()); + } +} From ed56093639d777ec0972cee93af6f71bd4489dad Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:46:15 +0900 Subject: [PATCH 201/685] =?UTF-8?q?feat(Statistics):=20RankingEntry=20DTO?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincoreredis/rank/dto/RankingEntry.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/dto/RankingEntry.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/dto/RankingEntry.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/dto/RankingEntry.java new file mode 100644 index 00000000..9dcc2e45 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/dto/RankingEntry.java @@ -0,0 +1,18 @@ +package com.nowait.domaincoreredis.rank.dto; + +import lombok.Getter; + +@Getter +public class RankingEntry { + private final Long storeId; + private final Integer totalSales; + private final Long currentRank; + private final Integer delta; + + public RankingEntry(Long storeId, Integer totalSales, Long currentRank, Integer delta) { + this.storeId = storeId; + this.totalSales = totalSales; + this.currentRank = currentRank; + this.delta = delta; + } +} From 3b96246cf7ba668d8c6f53342f1690c94246233c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:46:23 +0900 Subject: [PATCH 202/685] =?UTF-8?q?feat(Statistics):=20StoreInfo=20DTO=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/dto/StoreInfo.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java new file mode 100644 index 00000000..5dd322a8 --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java @@ -0,0 +1,18 @@ +package com.nowait.domainadminrdb.statistic.dto; + +import lombok.Getter; + +@Getter +public class StoreInfo { + private Long storeId; + private String storeName; + private Long departmentId; + private String departmentName; + + public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) { + this.storeId = storeId; + this.storeName = storeName; + this.departmentId = departmentId; + this.departmentName = departmentName; + } +} From d86d465c7d9cd8c392d0445444b32642977900b9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:46:29 +0900 Subject: [PATCH 203/685] =?UTF-8?q?feat(Statistics):=20StoreSales=20DTO=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domainadminrdb/statistic/dto/StoreSales.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreSales.java diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreSales.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreSales.java new file mode 100644 index 00000000..442eacc8 --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreSales.java @@ -0,0 +1,14 @@ +package com.nowait.domainadminrdb.statistic.dto; + +import lombok.Getter; + +@Getter +public class StoreSales { + private final Long storeId; + private final Integer totalSales; + + public StoreSales(Long storeId, Integer totalSales) { + this.storeId = storeId; + this.totalSales = totalSales; + } +} From 65c0e4be291344e3e87ff333f8bfdd42768e7210 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:46:34 +0900 Subject: [PATCH 204/685] =?UTF-8?q?feat(Statistics):=20StoreRankingDto=20D?= =?UTF-8?q?TO=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/dto/StoreRankingDto.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java new file mode 100644 index 00000000..a5d0601a --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java @@ -0,0 +1,25 @@ +package com.nowait.applicationadmin.statistic.dto; + +import lombok.Getter; + +@Getter +public class StoreRankingDto { + private final Long storeId; + private final String storeName; + private final Long departmentId; + private final String departmentName; + private final Integer totalSales; + private final Long currentRank; + private final Integer delta; + + public StoreRankingDto(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales, + Long currentRank, Integer delta) { + this.storeId = storeId; + this.storeName = storeName; + this.departmentId = departmentId; + this.departmentName = departmentName; + this.totalSales = totalSales; + this.currentRank = currentRank; + this.delta = delta; + } +} From ac1655474e096add74f403b8823b0bd002507740 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:47:03 +0900 Subject: [PATCH 205/685] =?UTF-8?q?refactor(Order):=20=ED=86=B5=EA=B3=84?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 16246fbc..7022486b 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -17,8 +17,6 @@ import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.applicationadmin.order.service.OrderService; import com.nowait.common.api.ApiUtils; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; -import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; @@ -65,34 +63,4 @@ public ResponseEntity updateOrderStatus( .status(HttpStatus.OK) .body(ApiUtils.success(response)); } - - @GetMapping("/sales") - @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") - @ApiResponse(responseCode = "200", description = "오늘의 매출 조회 성공") - public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails memberDetails) { - OrderSalesSumDetail sales = orderService.getSaleSumByStoreId(memberDetails); - - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - sales - ) - ); - } - - @GetMapping("/top-sales") - @Operation(summary = "오늘의 매출 상위 5개 주점 조회", description = "오늘의 매출이 가장 높은 상위 5개 주점을 조회합니다.") - @ApiResponse(responseCode = "200", description = "오늘의 매출 상위 5개 주점 조회 성공") - public ResponseEntity getTopSalesStores(@AuthenticationPrincipal MemberDetails memberDetails) { - List topSalesStoresDetail = orderService.getTop5StoresBySalesToday(memberDetails); - - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - topSalesStoresDetail - ) - ); - } } From a4a2b9beb4770cb172ae1f27068b4e2886c1761c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:47:16 +0900 Subject: [PATCH 206/685] =?UTF-8?q?refactor(Order):=20OrderCustomRepositor?= =?UTF-8?q?y=20=EC=83=81=EC=86=8D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/domaincorerdb/order/repository/OrderRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index 54c2b7b6..b6b0a12d 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -9,7 +9,7 @@ import com.nowait.domaincorerdb.order.entity.UserOrder; @Repository -public interface OrderRepository extends JpaRepository, OrderCustomRepository { +public interface OrderRepository extends JpaRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); From 2fbf75aa060b8d00ff5ccd848cbc8d6cf7ce7875 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:48:24 +0900 Subject: [PATCH 207/685] =?UTF-8?q?chore(Order):=20statistic=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 10 ++++++---- .../statistic}/dto/OrderSalesSumDetail.java | 2 +- .../statistic}/dto/TopSalesStoresDetail.java | 2 +- .../order/repository/OrderCustomRepository.java | 13 ------------- .../service/StoreRankCacheService.java | 10 ---------- 5 files changed, 8 insertions(+), 29 deletions(-) rename nowait-domain/{domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order => domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic}/dto/OrderSalesSumDetail.java (91%) rename nowait-domain/{domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order => domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic}/dto/TopSalesStoresDetail.java (92%) delete mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java delete mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index c0d3db14..9f808caa 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -9,8 +9,9 @@ import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; -import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; -import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; +import com.nowait.domainadminrdb.statistic.dto.OrderSalesSumDetail; +import com.nowait.domainadminrdb.statistic.dto.TopSalesStoresDetail; +import com.nowait.domainadminrdb.statistic.repository.StatisticCustomRepository; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; @@ -30,6 +31,7 @@ @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; + private final StatisticCustomRepository statisticCustomRepository; private final UserRepository userRepository; private final StoreRepository storeRepository; @@ -67,7 +69,7 @@ public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails) { throw new OrderViewUnauthorizedException(); } - return orderRepository.findSalesSumByStoreId(storeId); + return statisticCustomRepository.findSalesSumByStoreId(storeId); } @Transactional(readOnly = true) @@ -79,6 +81,6 @@ public List getTop5StoresBySalesToday(MemberDetails member throw new OrderViewUnauthorizedException(); } - return orderRepository.getTop4PlusMine(storeId); + return statisticCustomRepository.getTop4PlusMine(storeId); } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java similarity index 91% rename from nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java rename to nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java index fe3b25ee..e3ac3ef8 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/OrderSalesSumDetail.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.order.dto; +package com.nowait.domainadminrdb.statistic.dto; import lombok.Builder; import lombok.Getter; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/TopSalesStoresDetail.java similarity index 92% rename from nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java rename to nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/TopSalesStoresDetail.java index acc0d2d3..156e0249 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/dto/TopSalesStoresDetail.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/TopSalesStoresDetail.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.order.dto; +package com.nowait.domainadminrdb.statistic.dto; import lombok.Builder; import lombok.Getter; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java deleted file mode 100644 index 77d2747d..00000000 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.nowait.domaincorerdb.order.repository; - -import java.util.List; - -import com.nowait.domaincorerdb.order.dto.OrderSalesSumDetail; -import com.nowait.domaincorerdb.order.dto.TopSalesStoresDetail; - -public interface OrderCustomRepository { - - OrderSalesSumDetail findSalesSumByStoreId(Long storeId); - - List getTop4PlusMine(Long storeId); -} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java deleted file mode 100644 index 444aedcf..00000000 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nowait.domaincoreredis.service; - -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -@Service -public class StoreRankCacheService { - - private RedisTemplate redisTemplate; -} From ba33b2657bb9fe7ac01f6e1dcdcf82a2967ed57d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 19:48:28 +0900 Subject: [PATCH 208/685] =?UTF-8?q?chore(Order):=20statistic=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/domainadminrdb}/config/QueryDslConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename nowait-domain/{domain-core-rdb/src/main/java/com/nowait/domaincorerdb => domain-admin-rdb/src/main/java/com/nowait/domainadminrdb}/config/QueryDslConfig.java (91%) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java similarity index 91% rename from nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java rename to nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java index f75d497b..6c04df39 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.config; +package com.nowait.domainadminrdb.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; From 5b8260a6963f5d154b84888567097f011fdf503e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 20:18:56 +0900 Subject: [PATCH 209/685] =?UTF-8?q?refactor:=20redis=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincoreredis/config/RedisConfig.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java index 57e8b919..b0967e0f 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -16,9 +17,16 @@ public class RedisConfig { @Value("${spring.data.redis.port}") private int port; + @Value("${spring.data.redis.password}") + private String password; + @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(host, port); + RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(); + redisConfiguration.setHostName(host); + redisConfiguration.setPort(port); + redisConfiguration.setPassword(password); + return new LettuceConnectionFactory(redisConfiguration); } @Bean From ae8328ac0b55257e256df8750bfc2762c9b3682a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 20:50:51 +0900 Subject: [PATCH 210/685] =?UTF-8?q?refactor:=20null=20=EC=95=88=EC=A0=95?= =?UTF-8?q?=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rank/repository/RankingQueryRepositoryImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java index 9fcc721d..fa792f2f 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java @@ -50,7 +50,8 @@ public List findTopStoresWithUser(String key, Long userStoreId, in if (userZero != null && userZero >= topN) { List top4 = entries.subList(0, topN - 1); - Integer userSales = redisTemplate.opsForZSet().score(key, userStoreId.toString()).intValue(); + Double userScore = redisTemplate.opsForZSet().score(key, userStoreId.toString()); + Integer userSales = userScore != null ? userScore.intValue() : 0; top4.add(new RankingEntry( userStoreId, From c14108410384c5647ed4926259c1d8577f5c7c60 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 20:51:58 +0900 Subject: [PATCH 211/685] =?UTF-8?q?refactor:=20StoreInfo=20DTO=20final=20?= =?UTF-8?q?=EB=B6=88=EB=B3=80=EC=84=B1=20=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/domainadminrdb/statistic/dto/StoreInfo.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java index 5dd322a8..893503bf 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java @@ -4,10 +4,10 @@ @Getter public class StoreInfo { - private Long storeId; - private String storeName; - private Long departmentId; - private String departmentName; + private final Long storeId; + private final String storeName; + private final Long departmentId; + private final String departmentName; public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) { this.storeId = storeId; From eacc152622ce086b0d3f5cc99d7e3f5d321f6169 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 20:52:12 +0900 Subject: [PATCH 212/685] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/RankingRefreshScheduler.java | 34 +++++++++++-- .../rank/service/RankingQueryServiceImpl.java | 50 +++++++++++-------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index bd1f47a8..ebe363d5 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -37,6 +37,15 @@ public void init() { public void refresh() { log.info("RankingRefreshScheduler.refresh() called at {}", LocalDateTime.now()); + try { + doRefresh(); + } catch (Exception e) { + log.error("랭킹 데이터 갱신 중 오류 발생", e); + // 예외 발생 시 알림 또는 로깅 처리 + } + } + + private void doRefresh() { String nextKey = RedisKeyUtils.buildNextKey(); String currentKey = RedisKeyUtils.buildCurrentKey(); String previousKey = RedisKeyUtils.buildPreviousKey(); @@ -47,16 +56,31 @@ public void refresh() { // 2) DB에서 매출 합계 가져와 ZADD List salesList = statisticCustomRepository.findTotalSales(); + if (salesList.isEmpty()) { + log.warn("매출 데이터가 없습니다. 다음 스냅샷 키를 초기화합니다."); + return; + } + salesList.forEach(s -> rankingQueryRepository.addToRanking(nextKey, s.getStoreId(), s.getTotalSales()) ); // 3) 현재 스냅샷 키를 이전 스냅샷 키로 이동 - if (redis.hasKey(currentKey)) { - redis.rename(currentKey, previousKey); - } - if (redis.hasKey(nextKey)) { - redis.rename(nextKey, currentKey); + rotateKeys(currentKey, previousKey, nextKey); + } + + private void rotateKeys(String currentKey, String previousKey, String nextKey) { + try { + if (redis.hasKey(currentKey)) { + redis.rename(currentKey, previousKey); + } + if (redis.hasKey(nextKey)) { + redis.rename(nextKey, currentKey); + } + log.info("Keys rotated: current -> {}, previous -> {}, next -> {}", currentKey, previousKey, nextKey); + } catch (Exception e) { + log.error("Redis 키 교체 중 오류 발생", e); + throw new RuntimeException("랭킹 데이터 갱신 실패", e); } } } diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java index d3bc0050..7718f358 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java @@ -1,6 +1,7 @@ package com.nowait.domaincoreredis.rank.service; import java.util.List; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -19,26 +20,33 @@ public class RankingQueryServiceImpl implements RankingQueryService { @Override public List getRankings(Long userStoreId, int topN) { // 1) 현재 스냅샷에서 상위 topN 주점 조회 - List entries = rankingQueryRepository.findTopStores(RedisKeyUtils.buildCurrentKey(), topN); - - // 2) 사용자 주점이 topN 밖이면 4위까지 + 사용자 주점 - entries = rankingQueryRepository.findTopStoresWithUser(RedisKeyUtils.buildCurrentKey(), userStoreId, topN, entries); - - // 3) 이전 스냅샷에서 등락 정보 조회 - for (int i = 0; i < entries.size(); i++) { - var entry = entries.get(i); - Long prevZero = rankingQueryRepository.findPrevRank(RedisKeyUtils.buildPreviousKey(), entry.getStoreId()); - long prevRank = (prevZero == null ? entry.getCurrentRank() : prevZero + 1); - int delta = (int)(prevRank - entry.getCurrentRank()); - - entries.set(i, new RankingEntry( - entry.getStoreId(), - entry.getTotalSales(), - entry.getCurrentRank(), - delta - )); - } - - return entries; + List entries = getCurrentRankings(userStoreId, topN); + // 2) 이전 스냅샷에서 등락 정보 조회 + return calculateRankingDeltas(entries); + } + + private List getCurrentRankings(Long userStoreId, int topN) { + String currentKey = RedisKeyUtils.buildCurrentKey(); + List entries = rankingQueryRepository.findTopStores(currentKey, topN); + return rankingQueryRepository.findTopStoresWithUser(currentKey, userStoreId, topN, entries); + } + + private List calculateRankingDeltas(List entries) { + String previousKey = RedisKeyUtils.buildPreviousKey(); + return entries.stream() + .map(entry -> calculateDeltaForEntry(entry, previousKey)) + .collect(Collectors.toList()); + } + + private RankingEntry calculateDeltaForEntry(RankingEntry entry, String previousKey) { + Long prevZero = rankingQueryRepository.findPrevRank(previousKey, entry.getStoreId()); + long prevRank = (prevZero == null ? entry.getCurrentRank() : prevZero + 1); + int delta = (int)(prevRank - entry.getCurrentRank()); + return new RankingEntry( + entry.getStoreId(), + entry.getTotalSales(), + entry.getCurrentRank(), + delta + ); } } From e04cb537fa37f7812a02f78b23e6cd99f151e05b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 20:54:02 +0900 Subject: [PATCH 213/685] =?UTF-8?q?chore:=20QueryDSL=20Q=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1=20=EA=B4=80=EB=A0=A8=20TODO=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-domain/domain-core-rdb/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-domain/domain-core-rdb/build.gradle b/nowait-domain/domain-core-rdb/build.gradle index 48bcad51..954fbd42 100644 --- a/nowait-domain/domain-core-rdb/build.gradle +++ b/nowait-domain/domain-core-rdb/build.gradle @@ -39,6 +39,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.26' //QueryDsl + // TODO Q클래스 생성시 오류 발생 해결 필요 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" From 5fca343d4153f34b5942fcbd552f4e7fdb5a820d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 20:58:14 +0900 Subject: [PATCH 214/685] =?UTF-8?q?refactor:=20Redis=20=ED=82=A4=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4=20=EA=B3=BC=EC=A0=95=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9B=90=EC=9E=90=EC=84=B1=20=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/RankingRefreshScheduler.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index ebe363d5..4e398c64 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @@ -33,7 +34,7 @@ public void init() { refresh(); } - @Scheduled(cron = "0 */5 * * * *") // 매 5분마다 실행 + @Scheduled(cron = "* */5 * * * *") // 매 5분마다 실행 public void refresh() { log.info("RankingRefreshScheduler.refresh() called at {}", LocalDateTime.now()); @@ -71,12 +72,21 @@ private void doRefresh() { private void rotateKeys(String currentKey, String previousKey, String nextKey) { try { - if (redis.hasKey(currentKey)) { - redis.rename(currentKey, previousKey); - } - if (redis.hasKey(nextKey)) { - redis.rename(nextKey, currentKey); - } + redis.execute((RedisCallback)connection -> { + // 현재 스냅샷 키를 이전 스냅샷 키로 이동하고, 다음 스냅샷 키를 현재 스냅샷 키로 이동 + if (redis.hasKey(previousKey)) { + redis.delete(previousKey); + } + // 현재 스냅샷 키를 다음 스냅샷 키로 이동 + if (redis.hasKey(currentKey)) { + redis.rename(currentKey, previousKey); + } + // 다음 스냅샷 키가 존재하면 현재 스냅샷 키로 이동 + if (redis.hasKey(nextKey)) { + redis.rename(nextKey, currentKey); + } + return null; + }); log.info("Keys rotated: current -> {}, previous -> {}, next -> {}", currentKey, previousKey, nextKey); } catch (Exception e) { log.error("Redis 키 교체 중 오류 발생", e); From c3c530a8a1d1fefdcfa4cf6a6717bb023de434f8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 22:52:27 +0900 Subject: [PATCH 215/685] =?UTF-8?q?refactor:=20=EC=8A=A4=EC=BC=80=EC=A5=B4?= =?UTF-8?q?=EB=9F=AC=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 2 ++ .../statistic/scheduler/RankingRefreshScheduler.java | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1098599d..b538931a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @EnableJpaAuditing +@EnableScheduling @SpringBootApplication public class ApiAdminApplication { public static void main(String[] args) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index 4e398c64..c8de3f59 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -8,6 +8,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; import com.nowait.domainadminrdb.statistic.dto.StoreSales; import com.nowait.domainadminrdb.statistic.repository.StatisticCustomRepository; @@ -18,8 +19,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -@Configuration -@EnableScheduling + +@Component @RequiredArgsConstructor @Slf4j public class RankingRefreshScheduler { From 987130e24cb455a620818b4709d3ef57638df9f7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 16 Jul 2025 22:56:01 +0900 Subject: [PATCH 216/685] =?UTF-8?q?refactor:=20=ED=81=AC=EB=A1=A0=20?= =?UTF-8?q?=ED=91=9C=ED=98=84=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/scheduler/RankingRefreshScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index c8de3f59..ed81d051 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -35,7 +35,7 @@ public void init() { refresh(); } - @Scheduled(cron = "* */5 * * * *") // 매 5분마다 실행 + @Scheduled(cron = "0 */5 * * * *") // 매 5분마다 실행 public void refresh() { log.info("RankingRefreshScheduler.refresh() called at {}", LocalDateTime.now()); From 0bafc93163782e9f299b13aa6e550c00b7e77a93 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:26:45 +0900 Subject: [PATCH 217/685] =?UTF-8?q?feat(StoreImage):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/controller/StoreController.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index 29b7287c..f1dbc8d2 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -27,7 +27,6 @@ public class StoreController { private final StoreService storeService; - @GetMapping("/all-stores") @Operation(summary = "모든 주점 조회", description = "모든 주점을 조회합니다.") @ApiResponse(responseCode = "200", description = "모든 주점 조회 성공") @@ -50,7 +49,11 @@ public ResponseEntity getAllStores() { public ResponseEntity getAllStores(Pageable pageable) { return ResponseEntity .ok() - .body(ApiUtils.success(storeService.getAllStoresByPage(pageable))); + .body( + ApiUtils.success( + storeService.getAllStoresByPage(pageable) + ) + ); } @GetMapping("/{storeId}") From 8410ed34e6d83afb322fa44998dcd905d1fa1de7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:26:58 +0900 Subject: [PATCH 218/685] =?UTF-8?q?feat(StoreImage):=20store=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20enum=20=ED=83=80=EC=9E=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/store/entity/ImageType.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java new file mode 100644 index 00000000..db1ba0a5 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java @@ -0,0 +1,18 @@ +package com.nowait.domaincorerdb.store.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "이미지 타입 Enum") +public enum ImageType { + @Schema(description = "주점 프로필 사진") + PROFILE("프로필 사진"), + + @Schema(description = "주점 배너 사진") + BANNER("배너 사진"); + + private final String description; +} From ca2fcf563cccf3cc6af87f2063569357bf8e3c65 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:27:02 +0900 Subject: [PATCH 219/685] =?UTF-8?q?feat(StoreImage):=20store=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20enum=20=ED=83=80=EC=9E=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincorerdb/store/entity/StoreImage.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java index e8c5d270..a132c4ce 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java @@ -4,6 +4,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -36,4 +38,8 @@ public class StoreImage extends BaseTimeEntity { @Column(nullable = false, length = 500) private String fileKey; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private ImageType imageType; } From 3adaf4f4554f3f09f3dd111bf6d1e42d0039d6c4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:27:08 +0900 Subject: [PATCH 220/685] =?UTF-8?q?feat(StoreImage):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StoreImageController.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java index 61ed67f2..ad0a88d3 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java @@ -29,7 +29,7 @@ public class StoreImageController { private final StoreImageService storeImageService; - @PostMapping("/store-images/{storeId}") + @PostMapping("/banner-images/{storeId}") @Operation( summary = "주점 이미지 업로드", description = "주점에 이미지를 업로드합니다. 최대 10개의 이미지 파일을 업로드할 수 있습니다." @@ -64,6 +64,33 @@ public ResponseEntity uploadStoreImage( ); } + @PostMapping("/profile-images/{storeId}") + @Operation( + summary = "주점 프로필 이미지 업로드", + description = "주점의 프로필 이미지를 업로드합니다. 단일 이미지 파일을 업로드할 수 있습니다." + ) + @ApiResponse(responseCode = "201", description = "주점 프로필 이미지 업로드 성공") + public ResponseEntity uploadStoreProfileImage( + @PathVariable Long storeId, + @RequestParam("file") MultipartFile file + ) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("빈 파일은 업로드할 수 없습니다."); + } + if (file.getSize() > 10 * 1024 * 1024) { // 10MB 제한 + throw new IllegalArgumentException("파일 크기는 10MB를 초과할 수 없습니다."); + } + + StoreImageUploadResponse response = storeImageService.saveProfileImage(storeId, file); + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success( + response + ) + ); + } + @DeleteMapping("/store-images/{storeImageId}") @Operation( summary = "주점 이미지 삭제", From 1d494b9d95a4f551b8332b7f90112a74657ce3ca Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:27:42 +0900 Subject: [PATCH 221/685] =?UTF-8?q?feat(StoreImage):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=83=80=EC=9E=85=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=B0=BE=EB=8A=94=20findByStoreStoreIdAndImageType?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/store/repository/StoreImageRepository.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java index 80448ab8..1ad6929e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java @@ -1,10 +1,12 @@ package com.nowait.domaincorerdb.store.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; @@ -12,4 +14,6 @@ public interface StoreImageRepository extends JpaRepository { List findByStore(Store store); + + Optional findByStoreStoreIdAndImageType(Long storeId, ImageType imageType); } From 5f700673d2ac5e97beddbc2554a02daf1da73b5b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:28:07 +0900 Subject: [PATCH 222/685] =?UTF-8?q?feat(StoreImage):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreImageService.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java index aef083e2..772acdcf 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.springframework.stereotype.Service; @@ -10,6 +11,7 @@ import com.nowait.applicationadmin.store.dto.StoreImageUploadResponse; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; import com.nowait.domaincorerdb.store.exception.StoreImageEmptyException; @@ -31,7 +33,8 @@ public class StoreImageService { @Transactional public List saveAll(Long storeId, List files) { - if (files == null || files.isEmpty()) throw new StoreImageEmptyException(); + if (files == null || files.isEmpty()) + throw new StoreImageEmptyException(); String type = "store"; Store store = storeRepository.findById(storeId) @@ -60,6 +63,7 @@ public List saveAll(Long storeId, List .store(store) .imageUrl(uploadResult.url()) .fileKey(uploadResult.key()) + .imageType(ImageType.BANNER) .build(); storeImageRepository.save(storeImage); @@ -69,6 +73,36 @@ public List saveAll(Long storeId, List return imageUploadResponses; } + @Transactional + public StoreImageUploadResponse saveProfileImage(Long storeId, MultipartFile file) { + + String type = "store"; + Store store = storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + + Optional existingProfileImage = storeImageRepository.findByStoreStoreIdAndImageType(store.getStoreId(), + ImageType.PROFILE); + + existingProfileImage.ifPresent(profile -> { + s3Service.delete(profile.getFileKey()); + storeImageRepository.delete(profile); + }); + + S3Service.S3UploadResult uploadResult = s3Service.upload(type, storeId, file).join(); + + // StoreImage 엔티티 생성 및 저장 + StoreImage storeImage = StoreImage.builder() + .store(store) + .imageUrl(uploadResult.url()) + .fileKey(uploadResult.key()) + .imageType(ImageType.PROFILE) + .build(); + + storeImageRepository.save(storeImage); + + return StoreImageUploadResponse.fromEntity(storeImage); + } + @Transactional public void delete(Long storeImageId) { StoreImage storeImage = storeImageRepository.findById(storeImageId) From db52b17a223c10b67e12b00a4f27d580650dce63 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:28:20 +0900 Subject: [PATCH 223/685] =?UTF-8?q?feat(StoreImage):=20imageType=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/store/dto/StoreImageUploadResponse.java | 3 +++ .../applicationuser/store/dto/StoreImageUploadResponse.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java index a34ca422..c5407e0f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreImageUploadResponse.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.store.dto; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.StoreImage; import lombok.Builder; @@ -10,11 +11,13 @@ public class StoreImageUploadResponse { private final Long id; private final String imageUrl; + private final ImageType imageType; public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { return StoreImageUploadResponse.builder() .id(storeImage.getId()) .imageUrl(storeImage.getImageUrl()) + .imageType(storeImage.getImageType()) .build(); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java index 5ff57643..62822b3d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java @@ -1,5 +1,6 @@ package com.nowait.applicationuser.store.dto; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.StoreImage; import lombok.Builder; @@ -10,11 +11,13 @@ public class StoreImageUploadResponse { private final Long id; private final String imageUrl; + private final ImageType imageType; public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { return StoreImageUploadResponse.builder() .id(storeImage.getId()) .imageUrl(storeImage.getImageUrl()) + .imageType(storeImage.getImageType()) .build(); } } From 5773564fbcb31ca3a36966f770107e4af6399339 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:28:39 +0900 Subject: [PATCH 224/685] =?UTF-8?q?feat(StoreImage):=20profile,=20banner?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20=EB=B3=B4=EC=97=AC?= =?UTF-8?q?=EC=A3=BC=EB=8F=84=EB=A1=9D=20dto=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreReadDto.java | 19 ++++++++++++++++--- .../store/dto/StoreReadDto.java | 19 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java index b0497dd1..68dee6be 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; import lombok.AllArgsConstructor; @@ -18,12 +19,23 @@ public class StoreReadDto { private String name; private String location; private String description; - private List images; + private StoreImageUploadResponse profileImage; + private List bannerImages; private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; - public static StoreReadDto fromEntity(Store store, List images) { + public static StoreReadDto fromEntity(Store store, List allImages) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + return StoreReadDto.builder() .createdAt(store.getCreatedAt()) .storeId(store.getStoreId()) @@ -33,7 +45,8 @@ public static StoreReadDto fromEntity(Store store, List images; + private StoreImageUploadResponse profileImage; + private List bannerImages; private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; - public static StoreReadDto fromEntity(Store store, List images) { + public static StoreReadDto fromEntity(Store store, List allImages) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + return StoreReadDto.builder() .createdAt(store.getCreatedAt()) .storeId(store.getStoreId()) @@ -33,7 +45,8 @@ public static StoreReadDto fromEntity(Store store, List Date: Thu, 17 Jul 2025 00:42:08 +0900 Subject: [PATCH 225/685] =?UTF-8?q?refactor(StoreImage):=20NONE=20ENUM=20?= =?UTF-8?q?=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincorerdb/store/entity/ImageType.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java index db1ba0a5..b7564e93 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/ImageType.java @@ -12,7 +12,10 @@ public enum ImageType { PROFILE("프로필 사진"), @Schema(description = "주점 배너 사진") - BANNER("배너 사진"); + BANNER("배너 사진"), + + @Schema(description = "주점 배너 사진") + NONE("이미지 없음"); private final String description; } From 0bcfa29cdc10cd1fbd4db77c77ef9b393a647b97 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 00:47:33 +0900 Subject: [PATCH 226/685] =?UTF-8?q?refactor(StoreImage):=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=80=EC=A6=9D=20=EA=B3=B5=ED=86=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StoreImageController.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java index ad0a88d3..30e4202c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java @@ -40,19 +40,8 @@ public ResponseEntity uploadStoreImage( @RequestParam("files") List files ) { // TODO 관련 정책 확정되면 메서드로 분리 예정 - // 파일 개수 제한 검증 - if (files.isEmpty() || files.size() > 10) { - throw new IllegalArgumentException("파일은 1개 이상 10개 이하로 업로드해 주세요."); - } // 파일 크기 검증 - for (MultipartFile file : files) { - if (file.isEmpty()) { - throw new IllegalArgumentException("빈 파일은 업로드할 수 없습니다."); - } - if (file.getSize() > 10 * 1024 * 1024) { // 10MB 제한 - throw new IllegalArgumentException("파일 크기는 10MB를 초과할 수 없습니다."); - } - } + validateFiles(files); List response = storeImageService.saveAll(storeId, files); return ResponseEntity @@ -74,12 +63,7 @@ public ResponseEntity uploadStoreProfileImage( @PathVariable Long storeId, @RequestParam("file") MultipartFile file ) { - if (file == null || file.isEmpty()) { - throw new IllegalArgumentException("빈 파일은 업로드할 수 없습니다."); - } - if (file.getSize() > 10 * 1024 * 1024) { // 10MB 제한 - throw new IllegalArgumentException("파일 크기는 10MB를 초과할 수 없습니다."); - } + validateFileSize(file); StoreImageUploadResponse response = storeImageService.saveProfileImage(storeId, file); return ResponseEntity @@ -108,4 +92,22 @@ public ResponseEntity deleteStoreImage(@PathVariable Long imageId) { ) ); } + + private void validateFileSize(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("빈 파일은 업로드할 수 없습니다."); + } + if (file.getSize() > 10 * 1024 * 1024) { // 10MB 제한 + throw new IllegalArgumentException("파일 크기는 10MB를 초과할 수 없습니다."); + } + } + + private void validateFiles(List files) { + if (files.isEmpty() || files.size() > 10) { + throw new IllegalArgumentException("파일은 1개 이상 10개 이하로 업로드해 주세요."); + } + for (MultipartFile file : files) { + validateFileSize(file); + } + } } From c64fd7a60a925dd32c5e3838a7a8a4cf571edf17 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 17 Jul 2025 16:02:57 +0900 Subject: [PATCH 227/685] =?UTF-8?q?feat(Order):=20order=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EC=A3=BC=EB=AC=B8=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/dto/OrderItemListGetResponseDto.java | 5 ++++- .../nowait/applicationuser/order/service/OrderService.java | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java index 3df52b94..2238e09a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java @@ -2,6 +2,7 @@ import com.nowait.domaincorerdb.order.entity.OrderItem; +import com.nowait.domaincorerdb.order.entity.OrderStatus; import lombok.AllArgsConstructor; import lombok.Builder; @@ -17,13 +18,15 @@ public class OrderItemListGetResponseDto { private String menuName; private Integer quantity; private Integer price; + private OrderStatus status; - public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem) { + public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem,OrderStatus status) { return OrderItemListGetResponseDto.builder() .orderId(orderItem.getUserOrder().getId()) .menuName(orderItem.getMenu().getName()) .quantity(orderItem.getQuantity()) .price(orderItem.getMenu().getPrice()) + .status(status) .build(); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 1f0ed53c..3c76e5ba 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -99,8 +99,8 @@ public List getOrderItems(Long storeId, Long tableI // 2. OrderItem으로 변환 return userOrders.stream() - .flatMap(order -> order.getOrderItems().stream()) - .map(OrderItemListGetResponseDto::fromEntity) + .flatMap(order -> order.getOrderItems().stream() + .map(orderItem -> OrderItemListGetResponseDto.fromEntity(orderItem, order.getStatus()))) .toList(); } From 84985a08e9b4440d56c30b91bd7c7485716ffac3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 17:47:10 +0900 Subject: [PATCH 228/685] =?UTF-8?q?refactor(Statistics):=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EA=B0=9D=EC=B2=B4=EC=97=90=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/dto/StoreRankingDto.java | 4 ++- .../service/impl/RankingServiceImpl.java | 5 ++-- .../statistic/dto/StoreInfo.java | 4 ++- .../StatisticCustomRepositoryImpl.java | 29 ++++++++++++------- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java index a5d0601a..0c40c84f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java @@ -11,9 +11,10 @@ public class StoreRankingDto { private final Integer totalSales; private final Long currentRank; private final Integer delta; + private final String profileUrl; public StoreRankingDto(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales, - Long currentRank, Integer delta) { + Long currentRank, Integer delta, String profileUrl) { this.storeId = storeId; this.storeName = storeName; this.departmentId = departmentId; @@ -21,5 +22,6 @@ public StoreRankingDto(Long storeId, String storeName, Long departmentId, String this.totalSales = totalSales; this.currentRank = currentRank; this.delta = delta; + this.profileUrl = profileUrl; } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java index 9b5d764b..af206acc 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java @@ -45,7 +45,7 @@ public List getStatisticsRankings(MemberDetails memberDetails) // 1) Redis에서 Top4+내주점: storeId, totalSales, currentRank, delta List entries = rankingQuery.getRankings(userStoreId, 5); - // 2) DB에서 store 정보 가져오기 + // 2) redis 에서 storeId 정보 가져오기 List storeIds = entries.stream() .map(RankingEntry::getStoreId) .toList(); @@ -67,7 +67,8 @@ public List getStatisticsRankings(MemberDetails memberDetails) info.getDepartmentName(), e.getTotalSales(), e.getCurrentRank(), - e.getDelta() + e.getDelta(), + info.getProfileUrl() ); }) .collect(Collectors.toList()); diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java index 893503bf..c50981b7 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java @@ -8,11 +8,13 @@ public class StoreInfo { private final String storeName; private final Long departmentId; private final String departmentName; + private final String profileUrl; - public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) { + public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName, String profileUrl) { this.storeId = storeId; this.storeName = storeName; this.departmentId = departmentId; this.departmentName = departmentName; + this.profileUrl = profileUrl; } } diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java index 440c13a6..ee0d92a7 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java @@ -21,11 +21,17 @@ import com.nowait.domainadminrdb.statistic.dto.TopSalesStoresDetail; import com.nowait.domaincorerdb.department.entity.QDepartment; import com.nowait.domaincorerdb.order.entity.QUserOrder; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.QStore; +import com.nowait.domaincorerdb.store.entity.QStoreImage; import com.querydsl.core.Tuple; +import com.querydsl.core.types.Expression; import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.extern.slf4j.Slf4j; + @Repository +@Slf4j public class StatisticCustomRepositoryImpl implements StatisticCustomRepository { private final JPAQueryFactory queryFactory; @@ -35,8 +41,9 @@ public StatisticCustomRepositoryImpl(JPAQueryFactory queryFactory) { } private static final QUserOrder u = QUserOrder.userOrder; - private static final QStore s = store; + private static final QStore s = QStore.store; private static final QDepartment d = QDepartment.department; + private static final QStoreImage si = QStoreImage.storeImage; @Override public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { @@ -262,8 +269,6 @@ private Map getDepartmentNames(Set departmentIds) { return departmentNameMap; } - - // redis 사용하는 부분 @Override public List findTotalSales() { @@ -294,18 +299,20 @@ public List findTotalSales() { @Override public List findStoreInfoByIds(List storeIds) { List tuples = queryFactory - .select(s.storeId, s.name, store.departmentId, d.name) - .from(store) - .join(d).on(store.departmentId.eq(d.id)) - .where(store.storeId.in(storeIds)) + .select(s.storeId, s.name, store.departmentId, d.name, si.imageUrl.coalesce("")) + .from(s) + .join(d).on(s.departmentId.eq(d.id)) + .leftJoin(si).on(s.storeId.eq(si.store.storeId).and(si.imageType.eq(ImageType.PROFILE))) + .where(s.storeId.in(storeIds)) .fetch(); return tuples.stream() .map(t -> new StoreInfo( - t.get(store.storeId), - t.get(store.name), - t.get(store.departmentId), - t.get(d.name) + t.get(s.storeId), + t.get(s.name), + t.get(s.departmentId), + t.get(d.name), + t.get(si.imageUrl.coalesce("")) )) .collect(Collectors.toList()); } From c488e31d946dfa0771e37900e6cd0b4dd7a318f7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 17 Jul 2025 17:54:09 +0900 Subject: [PATCH 229/685] =?UTF-8?q?refactor(Statistics):=20QueryDSL=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B0=B8=EC=A1=B0=20=EB=AA=85=EC=B9=AD=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/repository/StatisticCustomRepositoryImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java index ee0d92a7..0985b9b9 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java @@ -25,7 +25,6 @@ import com.nowait.domaincorerdb.store.entity.QStore; import com.nowait.domaincorerdb.store.entity.QStoreImage; import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.extern.slf4j.Slf4j; @@ -299,7 +298,7 @@ public List findTotalSales() { @Override public List findStoreInfoByIds(List storeIds) { List tuples = queryFactory - .select(s.storeId, s.name, store.departmentId, d.name, si.imageUrl.coalesce("")) + .select(s.storeId, s.name, s.departmentId, d.name, si.imageUrl.coalesce("")) .from(s) .join(d).on(s.departmentId.eq(d.id)) .leftJoin(si).on(s.storeId.eq(si.store.storeId).and(si.imageType.eq(ImageType.PROFILE))) From b0ebfc92a686f4f031e3ef7df265f7021efccff8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:51:17 +0900 Subject: [PATCH 230/685] =?UTF-8?q?feat(event):=20event=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/build.gradle | 1 + nowait-domain/domain-redis/build.gradle | 1 + nowait-event/build.gradle | 57 +++++++++++++++++++++++++ settings.gradle | 1 + 4 files changed, 60 insertions(+) create mode 100644 nowait-event/build.gradle diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 028c8818..5568bb77 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation project(':nowait-domain:domain-admin-rdb') implementation project(':nowait-domain:domain-core-rdb') implementation project(':nowait-domain:domain-redis') + implementation project(':nowait-event') // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/nowait-domain/domain-redis/build.gradle b/nowait-domain/domain-redis/build.gradle index 8a6470e6..2beb40e2 100644 --- a/nowait-domain/domain-redis/build.gradle +++ b/nowait-domain/domain-redis/build.gradle @@ -22,6 +22,7 @@ repositories { dependencies { implementation project(':nowait-common') + implementation project(':nowait-event') implementation 'org.springframework.boot:spring-boot-starter-data-redis' diff --git a/nowait-event/build.gradle b/nowait-event/build.gradle new file mode 100644 index 00000000..6d9bc8dc --- /dev/null +++ b/nowait-event/build.gradle @@ -0,0 +1,57 @@ +import org.gradle.jvm.toolchain.JavaLanguageVersion + +plugins { + id 'java-library' +} + +jar { + enabled = true +} + + +group = 'com.nowait' +version = rootProject.version + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':nowait-common') + implementation project(':nowait-domain:domain-core-rdb') + + api 'org.springframework.boot:spring-boot-starter-data-jpa' + api 'jakarta.persistence:jakarta.persistence-api:3.1.0' + + // SPRING SECURITY + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-validation' + + + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + + compileOnly 'org.projectlombok:lombok:1.18.26' + annotationProcessor 'org.projectlombok:lombok:1.18.26' + + //QueryDsl + // TODO Q클래스 생성시 오류 발생 해결 필요 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // Jackson + api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' + + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' +} diff --git a/settings.gradle b/settings.gradle index 89991c90..79aeb646 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,5 @@ include 'nowait-domain:domain-core-rdb' include 'nowait-domain:domain-admin-rdb' include 'nowait-domain:domain-user-rdb' include 'nowait-domain:domain-redis' +include 'nowait-event' include 'nowait-infra' From bfa73da7a2e35e18860cb7a3b12d74bbc06b131a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:51:34 +0900 Subject: [PATCH 231/685] =?UTF-8?q?feat(event):=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=9D=B4=EB=B2=A4=ED=8A=B8,=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=EB=84=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/CookingCompleteListener.java | 31 +++++++++++++++++++ .../order/event/CookingCompleteEvent.java | 20 ++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java create mode 100644 nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java new file mode 100644 index 00000000..1555fd0d --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java @@ -0,0 +1,31 @@ +package com.nowait.domaincoreredis.rank.listener; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import com.nowait.domaincoreredis.rank.service.MenuCounterService; +import com.nowait.nowaitevent.order.event.CookingCompleteEvent; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CookingCompleteListener { + private final MenuCounterService menuCounterService; + + @TransactionalEventListener( + classes = CookingCompleteEvent.class, + phase = TransactionPhase.AFTER_COMMIT + ) + public void onCookingComplete(CookingCompleteEvent event) { + Long storeId = event.getStoreId(); + for (CookingCompleteEvent.Item item : event.getItems()) { + menuCounterService.incrementMenuCounter( + item.getMenuId(), + storeId, + item.getQuantity() + ); + } + } +} diff --git a/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java b/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java new file mode 100644 index 00000000..0b1a16b2 --- /dev/null +++ b/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java @@ -0,0 +1,20 @@ +package com.nowait.nowaitevent.order.event; + +import java.util.List; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CookingCompleteEvent { + private final Long storeId; + private final List items; + + @Getter + @RequiredArgsConstructor + public static class Item { + private final Long menuId; + private final int quantity; + } +} From aa8d0e40b7a99b1baa336c461f459605962ae1d6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:51:46 +0900 Subject: [PATCH 232/685] =?UTF-8?q?feat(event):=20MenuCounterService=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rank/service/MenuCounterService.java | 12 +++++ .../rank/service/MenuCounterServiceImpl.java | 52 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterService.java create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterService.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterService.java new file mode 100644 index 00000000..d1024785 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterService.java @@ -0,0 +1,12 @@ +package com.nowait.domaincoreredis.rank.service; + +import java.util.Set; + +import org.springframework.data.redis.core.ZSetOperations; + +public interface MenuCounterService { + + void incrementMenuCounter(Long menuId, Long storeId, int qty); + + Set> getTopMenus(Long storeId, int topN); +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java new file mode 100644 index 00000000..c49366b7 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java @@ -0,0 +1,52 @@ +package com.nowait.domaincoreredis.rank.service; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; + +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class MenuCounterServiceImpl implements MenuCounterService { + + private final String KEY_FMT = RedisKeyUtils.buildMenuKey(); + private final DateTimeFormatter DTF = RedisKeyUtils.buildMenuDateKey(); + private final RedisTemplate redis; + + @Override + public void incrementMenuCounter(Long menuId, Long storeId, int qty) { + + String date = LocalDate.now().format(DTF); + String key = String.format(KEY_FMT, storeId, date); + + redis.opsForZSet().incrementScore(key, menuId.toString(), qty); + + Long expirationTime = redis.getExpire(key); + if (expirationTime == null || expirationTime < 0) { + long secondsUntilMidnight = Duration.between( + LocalDateTime.now(), + LocalDate.now().plusDays(1).atStartOfDay() + ).getSeconds(); + + redis.expire(key, secondsUntilMidnight, TimeUnit.SECONDS); + } + } + + @Override + public Set> getTopMenus(Long storeId, int topN) { + String date = LocalDate.now().format(DTF); + String key = String.format(KEY_FMT, storeId, date); + + return redis.opsForZSet().reverseRangeWithScores(key, 0, topN - 1); + } +} From 416c8a25223f7c68cd1984aa933c40fe8e9eeb36 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:51:56 +0900 Subject: [PATCH 233/685] =?UTF-8?q?feat(event):=20PopularMenuDto=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/dto/PopularMenuDto.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java new file mode 100644 index 00000000..828be84a --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java @@ -0,0 +1,12 @@ +package com.nowait.applicationadmin.statistic.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class PopularMenuDto { + private Long menuId; + private String menuName; + private Long soldCount; +} From 359182f49857c14a9bc5377baef9b48b05bbbc46 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:52:06 +0900 Subject: [PATCH 234/685] =?UTF-8?q?feat(event):=20PopularMenuRedisService?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PopularMenuRedisService.java | 10 ++++ .../impl/PopularMenuRedisServiceImpl.java | 53 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/PopularMenuRedisService.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/PopularMenuRedisService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/PopularMenuRedisService.java new file mode 100644 index 00000000..95fd281c --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/PopularMenuRedisService.java @@ -0,0 +1,10 @@ +package com.nowait.applicationadmin.statistic.service; + +import java.util.List; + +import com.nowait.applicationadmin.statistic.dto.PopularMenuDto; +import com.nowait.domaincorerdb.user.entity.MemberDetails; + +public interface PopularMenuRedisService { + List getTodayTop5(MemberDetails memberDetails); +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java new file mode 100644 index 00000000..9d922992 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java @@ -0,0 +1,53 @@ +package com.nowait.applicationadmin.statistic.service.impl; + +import java.util.List; +import java.util.Set; + +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; + +import com.nowait.applicationadmin.statistic.dto.PopularMenuDto; +import com.nowait.applicationadmin.statistic.service.PopularMenuRedisService; +import com.nowait.common.enums.Role; +import com.nowait.domainadminrdb.statistic.exception.StatisticViewUnauthorizedException; +import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.rank.service.MenuCounterService; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PopularMenuRedisServiceImpl implements PopularMenuRedisService { + + private final MenuCounterService menuCounterService; + private final MenuRepository menuRepository; + private final UserRepository userRepository; + + @Override + public List getTodayTop5(MemberDetails memberDetails) { + + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Long storeId = user.getStoreId(); + + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new StatisticViewUnauthorizedException(); + } + + Set> tuples = menuCounterService.getTopMenus(storeId, 5); + + return tuples.stream() + .map(tuple -> { + Long menuId = Long.parseLong(tuple.getValue()); + String menuName = menuRepository.findById(menuId) + .map(menu -> menu.getName()) + .orElse("Unknown Menu"); + + return new PopularMenuDto(menuId, menuName, tuple.getScore().longValue()); + }) + .toList(); + } +} From dc692e6f1500b124fc023eeb09b2a076815ff9ea Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:52:14 +0900 Subject: [PATCH 235/685] =?UTF-8?q?feat(event):=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EA=B4=80=EB=A0=A8=20=ED=82=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincoreredis/common/util/RedisKeyUtils.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index fd05a3cb..7c584af4 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -1,11 +1,19 @@ package com.nowait.domaincoreredis.common.util; +import java.time.format.DateTimeFormatter; + public class RedisKeyUtils { + // Store rank keys private static final String KEY_CURRENT = "nowait:store:rank:current"; private static final String KEY_PREVIOUS = "nowait:store:rank:previous"; private static final String KEY_NEXT = "nowait:store:rank:next"; + // Menu rank keys + private static final String KEY_FMT = "popular:%d:%s"; + private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private RedisKeyUtils() { throw new UnsupportedOperationException("유틸리티 서비스는 인스턴스화 할 수 없습니다."); } @@ -21,4 +29,8 @@ public static String buildPreviousKey() { public static String buildNextKey() { return KEY_NEXT; } + + public static String buildMenuKey() { return KEY_FMT; } + + public static DateTimeFormatter buildMenuDateKey() { return DTF; } } From 45fd931740176a8e0a53746c92c02a4097b8fe6d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:52:21 +0900 Subject: [PATCH 236/685] =?UTF-8?q?feat(event):=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EA=B4=80=EB=A0=A8=20=EC=97=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StatisticsController.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java index 0ad9f0e2..2fea19c0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java @@ -10,7 +10,9 @@ import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationadmin.order.service.OrderService; +import com.nowait.applicationadmin.statistic.dto.PopularMenuDto; import com.nowait.applicationadmin.statistic.dto.StoreRankingDto; +import com.nowait.applicationadmin.statistic.service.PopularMenuRedisService; import com.nowait.applicationadmin.statistic.service.RankingService; import com.nowait.common.api.ApiUtils; import com.nowait.domainadminrdb.statistic.dto.OrderSalesSumDetail; @@ -29,6 +31,7 @@ public class StatisticsController { private final OrderService orderService; private final RankingService rankingService; + private final PopularMenuRedisService popularMenuRedisService; @GetMapping("/sales") @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") @@ -58,4 +61,20 @@ public ResponseEntity getTopSalesStores(@AuthenticationPrincipal MemberDetail ) ); } + + @GetMapping("/popular-menu") + @Operation(summary = "인기 메뉴 조회", description = "인기 메뉴를 조회합니다.") + @ApiResponse(responseCode = "200", description = "인기 메뉴 조회 성공") + public ResponseEntity getPopularMenu(@AuthenticationPrincipal MemberDetails memberDetails) { + + List popularMenu = popularMenuRedisService.getTodayTop5(memberDetails); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + popularMenu + ) + ); + } } From 31468eb4b9af8e3fddc89d1bdd3b3f3a741ea9b4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:52:45 +0900 Subject: [PATCH 237/685] =?UTF-8?q?refactor(Statistics):=20StoreRankingDto?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=20=EA=B0=84=EA=B2=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/statistic/dto/StoreRankingDto.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java index 0c40c84f..a32d6162 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java @@ -4,12 +4,12 @@ @Getter public class StoreRankingDto { - private final Long storeId; + private final Long storeId; private final String storeName; - private final Long departmentId; + private final Long departmentId; private final String departmentName; private final Integer totalSales; - private final Long currentRank; + private final Long currentRank; private final Integer delta; private final String profileUrl; From e5adca2ec16c3aea9e6b1590d4ca66ccd6e46ce2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 00:53:16 +0900 Subject: [PATCH 238/685] =?UTF-8?q?refactor(Order):=20=EC=A3=BC=EB=AC=B8?= =?UTF-8?q?=20COOKED=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20Redis?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 9f808caa..b2f8c701 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +25,7 @@ import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.nowaitevent.order.event.CookingCompleteEvent; import lombok.RequiredArgsConstructor; @@ -34,6 +36,7 @@ public class OrderService { private final StatisticCustomRepository statisticCustomRepository; private final UserRepository userRepository; private final StoreRepository storeRepository; + private final ApplicationEventPublisher publisher; @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { @@ -57,6 +60,23 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus throw new OrderUpdateUnauthorizedException(); } userOrder.updateStatus(newStatus); + + if (OrderStatus.COOKED.equals(newStatus)) { + List items = userOrder.getOrderItems().stream() + .map(item -> new CookingCompleteEvent.Item( + item.getMenu().getId(), + item.getQuantity() + )) + .toList(); + + publisher.publishEvent( + new CookingCompleteEvent( + userOrder.getStore().getStoreId(), + items + ) + ); + } + return OrderStatusUpdateResponseDto.fromEntity(userOrder); } From 8d053723979c950be80491921b55341633d42886 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 01:08:05 +0900 Subject: [PATCH 239/685] =?UTF-8?q?refactor(Statistics):=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowaitevent/order/event/CookingCompleteEvent.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java b/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java index 0b1a16b2..4db8b75d 100644 --- a/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java +++ b/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/CookingCompleteEvent.java @@ -2,19 +2,28 @@ import java.util.List; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public class CookingCompleteEvent { + + @NotNull private final Long storeId; + + @NotNull private final List items; @Getter @RequiredArgsConstructor public static class Item { + + @NotNull private final Long menuId; + + @NotNull private final int quantity; } } From 619d8ae1ae74160aa28e0973f0369a4d63249d99 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 01:08:14 +0900 Subject: [PATCH 240/685] =?UTF-8?q?fix(Statistics):=20N=20+=201=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/PopularMenuRedisServiceImpl.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java index 9d922992..01119cbb 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java @@ -1,7 +1,9 @@ package com.nowait.applicationadmin.statistic.service.impl; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; @@ -10,6 +12,7 @@ import com.nowait.applicationadmin.statistic.service.PopularMenuRedisService; import com.nowait.common.enums.Role; import com.nowait.domainadminrdb.statistic.exception.StatisticViewUnauthorizedException; +import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; @@ -39,12 +42,22 @@ public List getTodayTop5(MemberDetails memberDetails) { Set> tuples = menuCounterService.getTopMenus(storeId, 5); + List menuIds = tuples.stream() + .map(tuple -> Long.parseLong(tuple.getValue())) + .toList(); + + Map menuIdToNameMap = menuRepository.findAllById(menuIds) + .stream() + .collect(Collectors.toMap( + Menu::getId, + Menu::getName + )); + + return tuples.stream() .map(tuple -> { Long menuId = Long.parseLong(tuple.getValue()); - String menuName = menuRepository.findById(menuId) - .map(menu -> menu.getName()) - .orElse("Unknown Menu"); + String menuName = menuIdToNameMap.getOrDefault(menuId, "Unknown Menu"); return new PopularMenuDto(menuId, menuName, tuple.getScore().longValue()); }) From f865597fa259e2bc4f8b937c10d076c8681a5ed4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 01:15:07 +0900 Subject: [PATCH 241/685] =?UTF-8?q?feat(Statistics):=20MenuCounter=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/common/exception/ErrorMessage.java | 1 + .../rank/exception/MenuCounterUpdateException.java | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index fa83423b..db722455 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -60,6 +60,7 @@ public enum ErrorMessage { // Statistics STATISTIC_VIEW_UNAUTHORIZED("통계 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "statistics001"), + MENU_COUNTER_UPDATE("메뉴 카운터 업데이트 실패", "statistics002"), // image IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java new file mode 100644 index 00000000..071c0b33 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincoreredis.rank.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuCounterUpdateException extends RuntimeException { + public MenuCounterUpdateException(String message) { + super(ErrorMessage.MENU_COUNTER_UPDATE.getMessage()); + } +} From b8ceeb1606dc0a7a876f6da8495b1b6ae217a570 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 01:15:30 +0900 Subject: [PATCH 242/685] =?UTF-8?q?refactor(Statistics):=20TTL=20=EB=8D=94?= =?UTF-8?q?=20=EC=95=88=EC=A0=84=ED=95=9C=20=EB=B0=A9=EC=8B=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rank/service/MenuCounterServiceImpl.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java index c49366b7..b286b637 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java @@ -12,11 +12,14 @@ import org.springframework.stereotype.Service; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.rank.exception.MenuCounterUpdateException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Service @RequiredArgsConstructor +@Slf4j public class MenuCounterServiceImpl implements MenuCounterService { private final String KEY_FMT = RedisKeyUtils.buildMenuKey(); @@ -26,19 +29,25 @@ public class MenuCounterServiceImpl implements MenuCounterService { @Override public void incrementMenuCounter(Long menuId, Long storeId, int qty) { - String date = LocalDate.now().format(DTF); - String key = String.format(KEY_FMT, storeId, date); + try { + String date = LocalDate.now().format(DTF); + String key = String.format(KEY_FMT, storeId, date); + + redis.opsForZSet().incrementScore(key, menuId.toString(), qty); - redis.opsForZSet().incrementScore(key, menuId.toString(), qty); + Long expirationTime = redis.getExpire(key); + if (expirationTime == null || expirationTime < 0) { + long secondsUntilMidnight = Duration.between( + LocalDateTime.now(), + LocalDate.now().plusDays(1).atStartOfDay() + ).getSeconds(); - Long expirationTime = redis.getExpire(key); - if (expirationTime == null || expirationTime < 0) { - long secondsUntilMidnight = Duration.between( - LocalDateTime.now(), - LocalDate.now().plusDays(1).atStartOfDay() - ).getSeconds(); + redis.expire(key, Duration.ofSeconds(secondsUntilMidnight)); + } - redis.expire(key, secondsUntilMidnight, TimeUnit.SECONDS); + } catch (Exception e) { + log.error("Failed to increment menu counter for menuId: {}, storeId: {}", + menuId, storeId, e); + throw new MenuCounterUpdateException("메뉴 카운터 업데이트 실패"); } } From 704d39ed4309e0d69cde0a94219f7849369b9cb6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 01:17:59 +0900 Subject: [PATCH 243/685] =?UTF-8?q?refactor(Statistics):=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-event/build.gradle | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/nowait-event/build.gradle b/nowait-event/build.gradle index 6d9bc8dc..067d51c3 100644 --- a/nowait-event/build.gradle +++ b/nowait-event/build.gradle @@ -29,29 +29,12 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-data-jpa' api 'jakarta.persistence:jakarta.persistence-api:3.1.0' - // SPRING SECURITY - implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-validation' - - - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + testImplementation 'org.springframework.boot:spring-boot-starter-test' compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' - //QueryDsl - // TODO Q클래스 생성시 오류 발생 해결 필요 - implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" - - // Jackson - api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' - testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } From 3c716de2bec63370e7c50e6e73fdb4a030d6fab7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 18 Jul 2025 01:22:08 +0900 Subject: [PATCH 244/685] =?UTF-8?q?refactor(Statistics):=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rank/exception/MenuCounterUpdateException.java | 4 +--- .../domaincoreredis/rank/service/MenuCounterServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java index 071c0b33..fc8c13ab 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java @@ -3,7 +3,5 @@ import com.nowait.common.exception.ErrorMessage; public class MenuCounterUpdateException extends RuntimeException { - public MenuCounterUpdateException(String message) { - super(ErrorMessage.MENU_COUNTER_UPDATE.getMessage()); - } + public MenuCounterUpdateException() { super(ErrorMessage.MENU_COUNTER_UPDATE.getMessage()); } } diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java index b286b637..f2ffbce3 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/MenuCounterServiceImpl.java @@ -47,7 +47,7 @@ public void incrementMenuCounter(Long menuId, Long storeId, int qty) { } catch (Exception e) { log.error("Failed to increment menu counter for menuId: {}, storeId: {}", + menuId, storeId, e); - throw new MenuCounterUpdateException("메뉴 카운터 업데이트 실패"); + throw new MenuCounterUpdateException(); } } From d5aae49fcd318890ccdf07bc3d58a5e1de4f99e0 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 18 Jul 2025 17:22:46 +0900 Subject: [PATCH 245/685] =?UTF-8?q?feat(Order,Reservation):=20redis=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 주문 조회 응답 구조 변경 - redis 기반 사용자 예약 생성 기능 구현 --- .../menu/service/MenuImageService.java | 1 - .../user/serivce/UserService.java | 7 --- nowait-app-user-api/build.gradle | 5 ++ .../order/controller/OrderController.java | 3 +- .../OrderItemGroupByStatusResponseDto.java | 20 +++++++ .../dto/OrderItemListGetResponseDto.java | 1 - .../order/service/OrderService.java | 17 ++++-- .../controller/ReservationController.java | 19 ++++++ .../reservation/dto/WaitingResponseDto.java | 12 ++++ .../repository/WaitingRedisRepository.java | 59 +++++++++++++++++++ .../service/ReservationService.java | 34 +++++++++++ .../common/util/RedisKeyUtils.java | 7 +++ 12 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java index 6cf6f394..10c60ac8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java @@ -23,7 +23,6 @@ public class MenuImageService { private final MenuRepository menuRepository; private final MenuImageRepository menuImageRepository; private final S3Service s3Service; - @Transactional public MenuImageUploadResponse save(Long menuId, MultipartFile file) { if (file == null || file.isEmpty()) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java index 837d2b8b..85b29b47 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java @@ -43,7 +43,6 @@ public class UserService { @Transactional public ManagerSignupResponseDto signup(ManagerSignupRequestDto managerSignupRequestDto) { validateEmailDuplicated(managerSignupRequestDto); - validateNickNameDuplicated(managerSignupRequestDto.getNickname()); User user = managerSignupRequestDto.toEntity(); user.encodePassword(passwordEncoder); @@ -56,12 +55,6 @@ private void validateEmailDuplicated(ManagerSignupRequestDto managerSignupReques } ); } - private void validateNickNameDuplicated(String nickName) { - userRepository.findByNickname(nickName).ifPresent(member -> { - throw new IllegalArgumentException(); - } - ); - } @Transactional public ResponseEntity login(ManagerLoginRequestDto managerLoginRequestDto) { Authentication authentication = authenticationProvider.authenticate( diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index 6e3a1302..afce863a 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation project(':nowait-infra') // 사용자 관련 도메인 implementation project(':nowait-domain:domain-user-rdb') implementation project(':nowait-domain:domain-core-rdb') + implementation project(':nowait-domain:domain-redis') // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' @@ -48,6 +49,10 @@ dependencies { // SWAGGER implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // 기타 필요 라이브러리 (예: Lombok) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 43be357d..9bc3b061 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -13,6 +13,7 @@ import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; +import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto; import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; import com.nowait.applicationuser.order.service.OrderService; import com.nowait.common.api.ApiUtils; @@ -58,7 +59,7 @@ public ResponseEntity getOrderItems( HttpSession session ) { String sessionId = session.getId(); - List orderItems = orderService.getOrderItems(storeId, tableId, sessionId); + List orderItems = orderService.getOrderItemsGroupByStatus(storeId, tableId, sessionId); return ResponseEntity. status(HttpStatus.OK) .body( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java new file mode 100644 index 00000000..84d423e9 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java @@ -0,0 +1,20 @@ +package com.nowait.applicationuser.order.dto; + +import java.util.List; + +import com.nowait.domaincorerdb.order.entity.OrderStatus; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OrderItemGroupByStatusResponseDto { + private OrderStatus status; + private List items; +} + diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java index 2238e09a..698dddaa 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java @@ -26,7 +26,6 @@ public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem,OrderSt .menuName(orderItem.getMenu().getName()) .quantity(orderItem.getQuantity()) .price(orderItem.getMenu().getPrice()) - .status(status) .build(); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 3c76e5ba..5444749f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -14,6 +14,7 @@ import com.nowait.applicationuser.order.dto.CartItemDto; import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; +import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto; import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.repository.MenuRepository; @@ -93,18 +94,26 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId, } @Transactional(readOnly = true) - public List getOrderItems(Long storeId, Long tableId, String sessionId) { - // 1. UserOrder 목록 조회 (storeId, tableId, sessionId 기준) + public List getOrderItemsGroupByStatus(Long storeId, Long tableId, String sessionId) { List userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId); - // 2. OrderItem으로 변환 - return userOrders.stream() + // flatMap으로 OrderItem + status로 펼친 뒤, status별로 groupBy + Map> grouped = userOrders.stream() .flatMap(order -> order.getOrderItems().stream() .map(orderItem -> OrderItemListGetResponseDto.fromEntity(orderItem, order.getStatus()))) + .collect(Collectors.groupingBy(OrderItemListGetResponseDto::getStatus)); + + // status별로 responseDto로 변환 + return grouped.entrySet().stream() + .map(entry -> OrderItemGroupByStatusResponseDto.builder() + .status(entry.getKey()) + .items(entry.getValue()) + .build()) .toList(); } + private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { if (storeId == null || tableId == null || orderCreateRequestDto == null) { throw new OrderParameterEmptyException(); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 60c32009..2087ed21 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -11,6 +11,7 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; +import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; import com.nowait.applicationuser.reservation.service.ReservationService; import com.nowait.common.api.ApiUtils; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -44,4 +45,22 @@ public ResponseEntity create( ) ); } + + @PostMapping("/create/redis/{storeId}") + @Operation(summary = "예약 생성", description = "특정 주점에 대한 예약하기 생성") + @ApiResponse(responseCode = "201", description = "예약 생성") + public ResponseEntity createQueue( + @PathVariable Long storeId, + @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @RequestBody ReservationCreateRequestDto requestDto + ) { + WaitingResponseDto response = reservationService.registerWaiting(storeId,customOAuth2User,requestDto); + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success( + response + ) + ); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java new file mode 100644 index 00000000..36527069 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java @@ -0,0 +1,12 @@ +package com.nowait.applicationuser.reservation.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class WaitingResponseDto { + private final int rank; + private final boolean reserved; // true면 예약, false면 대기 + private final int partySize; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java new file mode 100644 index 00000000..aca547e3 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java @@ -0,0 +1,59 @@ +package com.nowait.applicationuser.reservation.repository; + + +import java.util.Set; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class WaitingRedisRepository { + private final StringRedisTemplate redisTemplate; + + + // 중복 등록 방지: 이미 있으면 추가X + public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, long timestamp) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + + Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); + if (Boolean.TRUE.equals(added)) { + redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); + } + return Boolean.TRUE.equals(added); + } + + public Integer getPartySize(Long storeId, String userId) { + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + Object value = redisTemplate.opsForHash().get(partyKey, userId); + return value == null ? null : Integer.valueOf(value.toString()); + } + + public Long getRank(Long storeId, String userId) { + String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + Set members = redisTemplate.opsForZSet().range(key, 0, -1); + if (members == null) return null; + int idx = 0; + for (String m : members) { + if (m.startsWith(userId + ":")) { + return (long) idx; + } + idx++; + } + return null; + } + + public Long getWaitingCount(Long storeId) { + String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + return redisTemplate.opsForZSet().zCard(key); + } + + public void removeWaiting(Long storeId, String userId) { + String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + redisTemplate.opsForZSet().remove(key, userId); + } +} + + diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index d5dde8ab..25037d8d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -8,6 +8,8 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; +import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; @@ -30,6 +32,38 @@ public class ReservationService { private final ReservationRepository reservationRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; + private final WaitingRedisRepository waitingRedisRepository; + + public WaitingResponseDto registerWaiting( + Long storeId,CustomOAuth2User customOAuth2User,ReservationCreateRequestDto requestDto + ) { + String userId = customOAuth2User.getUserId().toString(); + long timestamp = System.currentTimeMillis(); + + // 중복 등록 방지 + if (waitingRedisRepository.getRank(storeId, userId) != null) + throw new IllegalArgumentException("Already registered"); + + // 예약 신청 유저 큐(queue)에 추가 + boolean added = waitingRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), timestamp); + // 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환 + Long rank = waitingRedisRepository.getRank(storeId, userId); + return WaitingResponseDto.builder() + .rank(rank == null ? -1 : rank.intValue() + 1) + .partySize(requestDto.getPartySize() == null ? 0 : requestDto.getPartySize()) + .build(); + } + + public WaitingResponseDto myWaitingInfo(Long storeId, String userId) { + Long rank = waitingRedisRepository.getRank(storeId, userId); + return WaitingResponseDto.builder() + .rank(rank == null ? -1 : rank.intValue() + 1) + .build(); + } + + public void cancelWaiting(Long storeId, String userId) { + waitingRedisRepository.removeWaiting(storeId, userId); + } @Transactional public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User, diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index 7c584af4..272b029f 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -13,6 +13,10 @@ public class RedisKeyUtils { private static final String KEY_FMT = "popular:%d:%s"; private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMdd"); + // Waiting keys + private static final String WAITING_KEY_PREFIX = "waiting:"; + private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:"; + private RedisKeyUtils() { throw new UnsupportedOperationException("유틸리티 서비스는 인스턴스화 할 수 없습니다."); @@ -33,4 +37,7 @@ public static String buildNextKey() { public static String buildMenuKey() { return KEY_FMT; } public static DateTimeFormatter buildMenuDateKey() { return DTF; } + + public static String buildWaitingKeyPrefix() { return WAITING_KEY_PREFIX; } + public static String buildWaitingPartySizeKeyPrefix() { return WAITING_PARTYSIZE_KEY_PREFIX; } } From 6fb5a1c26e0d08b1452cc2656bc66f2fb5b2ecad Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 18 Jul 2025 17:44:34 +0900 Subject: [PATCH 246/685] =?UTF-8?q?feat(Reservation):=20=EB=8C=80=EA=B8=B0?= =?UTF-8?q?=EC=97=B4=20=EC=B6=94=EA=B0=80=20=EC=8B=9C=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파라미터 유효성 검사 로직 - Store 예약 비활성화 여부 체크 로직 --- .../controller/ReservationController.java | 4 +-- .../repository/WaitingRedisRepository.java | 17 +++------- .../service/ReservationService.java | 31 +++++++++++++++---- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 2087ed21..0b546170 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -47,8 +47,8 @@ public ResponseEntity create( } @PostMapping("/create/redis/{storeId}") - @Operation(summary = "예약 생성", description = "특정 주점에 대한 예약하기 생성") - @ApiResponse(responseCode = "201", description = "예약 생성") + @Operation(summary = "대기열 등록", description = "특정 주점에 대한 대기열 등록") + @ApiResponse(responseCode = "201", description = "대기열 등록") public ResponseEntity createQueue( @PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User, diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java index aca547e3..7402b1c3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java @@ -12,7 +12,6 @@ public class WaitingRedisRepository { private final StringRedisTemplate redisTemplate; - // 중복 등록 방지: 이미 있으면 추가X public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, long timestamp) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; @@ -33,16 +32,7 @@ public Integer getPartySize(Long storeId, String userId) { public Long getRank(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; - Set members = redisTemplate.opsForZSet().range(key, 0, -1); - if (members == null) return null; - int idx = 0; - for (String m : members) { - if (m.startsWith(userId + ":")) { - return (long) idx; - } - idx++; - } - return null; + return redisTemplate.opsForZSet().rank(key, userId); } public Long getWaitingCount(Long storeId) { @@ -50,9 +40,12 @@ public Long getWaitingCount(Long storeId) { return redisTemplate.opsForZSet().zCard(key); } - public void removeWaiting(Long storeId, String userId) { + public boolean removeWaiting(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; redisTemplate.opsForZSet().remove(key, userId); + redisTemplate.opsForHash().delete(partyKey, userId); + return true; } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 25037d8d..c4fedcae 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -37,15 +37,20 @@ public class ReservationService { public WaitingResponseDto registerWaiting( Long storeId,CustomOAuth2User customOAuth2User,ReservationCreateRequestDto requestDto ) { + // Store 유효성 검증 추가 + Store store = storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + if (Boolean.FALSE.equals(store.getIsActive())) + throw new StoreWaitingDisabledException(); + String userId = customOAuth2User.getUserId().toString(); long timestamp = System.currentTimeMillis(); - // 중복 등록 방지 - if (waitingRedisRepository.getRank(storeId, userId) != null) - throw new IllegalArgumentException("Already registered"); - // 예약 신청 유저 큐(queue)에 추가 boolean added = waitingRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), timestamp); + if (!added) { + throw new IllegalArgumentException("Failed to add to waiting queue"); + } // 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환 Long rank = waitingRedisRepository.getRank(storeId, userId); return WaitingResponseDto.builder() @@ -55,14 +60,28 @@ public WaitingResponseDto registerWaiting( } public WaitingResponseDto myWaitingInfo(Long storeId, String userId) { + // 입력 검증 추가 + if (storeId == null || userId == null || userId.trim().isEmpty()) { + throw new IllegalArgumentException("Invalid storeId or userId"); + } Long rank = waitingRedisRepository.getRank(storeId, userId); + Integer partySize = waitingRedisRepository.getPartySize(storeId, userId); return WaitingResponseDto.builder() .rank(rank == null ? -1 : rank.intValue() + 1) + .partySize(partySize == null ? 0 : partySize) .build(); } - public void cancelWaiting(Long storeId, String userId) { - waitingRedisRepository.removeWaiting(storeId, userId); + public boolean cancelWaiting(Long storeId, String userId) { + if (storeId == null || userId == null || userId.trim().isEmpty()) { + throw new IllegalArgumentException("Invalid storeId or userId"); + } + // 대기열에서 제거 및 결과 반환 + boolean removed = waitingRedisRepository.removeWaiting(storeId, userId); + if (!removed) { + throw new IllegalArgumentException("Waiting not found"); + } + return removed; } @Transactional From 9085d4fb753be1bcd42b13484e43eec7ad646aad Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 18 Jul 2025 18:40:29 +0900 Subject: [PATCH 247/685] =?UTF-8?q?feat(Reservation):=20=EB=8C=80=EA=B8=B0?= =?UTF-8?q?=EC=97=B4=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 대기열 조회 및 취소 api 추가 --- .../controller/ReservationController.java | 35 +++++++++++++++++++ .../repository/WaitingRedisRepository.java | 2 +- .../service/ReservationService.java | 17 ++++++--- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 0b546170..eef6a538 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -3,6 +3,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -63,4 +65,37 @@ public ResponseEntity createQueue( ) ); } + + @GetMapping("/get/queue/redis/{storeId}") + @Operation(summary = "본인 대기열 조회", description = "특정 주점에 대한 본인 대기열 조회") + @ApiResponse(responseCode = "200", description = "본인 대기열 조회") + public ResponseEntity getQueue( + @PathVariable Long storeId, + @AuthenticationPrincipal CustomOAuth2User customOAuth2User + ) { + WaitingResponseDto response = reservationService.myWaitingInfo(storeId,customOAuth2User); + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); + } + + @DeleteMapping("/delete/queue/redis/{storeId}") + @Operation(summary = "내 대기열 취소", description = "특정 주점에 대한 대기열 취소") + @ApiResponse(responseCode = "200", description = "대기열 취소") + public ResponseEntity deleteQueue( + @PathVariable Long storeId, + @AuthenticationPrincipal CustomOAuth2User customOAuth2User + ) { + return ResponseEntity + .ok() + .body( + ApiUtils.success( + reservationService.cancelWaiting(storeId,customOAuth2User) + ) + ); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java index 7402b1c3..36f574db 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java @@ -27,7 +27,7 @@ public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, public Integer getPartySize(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; Object value = redisTemplate.opsForHash().get(partyKey, userId); - return value == null ? null : Integer.valueOf(value.toString()); + return Integer.valueOf(value.toString()); } public Long getRank(Long storeId, String userId) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index c4fedcae..d3310e95 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -11,6 +11,7 @@ import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; @@ -42,6 +43,12 @@ public WaitingResponseDto registerWaiting( .orElseThrow(StoreNotFoundException::new); if (Boolean.FALSE.equals(store.getIsActive())) throw new StoreWaitingDisabledException(); + // User Role 검증 추가 + User user = userRepository.findById(customOAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + if (user.getRole() == Role.MANAGER) { + throw new IllegalArgumentException("Manager cannot register waiting"); + } String userId = customOAuth2User.getUserId().toString(); long timestamp = System.currentTimeMillis(); @@ -59,9 +66,10 @@ public WaitingResponseDto registerWaiting( .build(); } - public WaitingResponseDto myWaitingInfo(Long storeId, String userId) { + public WaitingResponseDto myWaitingInfo(Long storeId, CustomOAuth2User customOAuth2User) { + String userId = customOAuth2User.getUserId().toString(); // 입력 검증 추가 - if (storeId == null || userId == null || userId.trim().isEmpty()) { + if (storeId == null || userId.trim().isEmpty()) { throw new IllegalArgumentException("Invalid storeId or userId"); } Long rank = waitingRedisRepository.getRank(storeId, userId); @@ -72,8 +80,9 @@ public WaitingResponseDto myWaitingInfo(Long storeId, String userId) { .build(); } - public boolean cancelWaiting(Long storeId, String userId) { - if (storeId == null || userId == null || userId.trim().isEmpty()) { + public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { + String userId = customOAuth2User.getUserId().toString(); + if (storeId == null || userId.trim().isEmpty()) { throw new IllegalArgumentException("Invalid storeId or userId"); } // 대기열에서 제거 및 결과 반환 From 82413c8bd4d0a3f7cece7cef48cef7f4b24f3359 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 18 Jul 2025 19:51:17 +0900 Subject: [PATCH 248/685] =?UTF-8?q?fix(Order):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=A3=BC=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - status 한번만 뜨도록 수정 --- .../repository/WaitingRedisRepository.java | 22 +++++++++++++++++++ .../order/controller/OrderController.java | 1 - .../order/dto/OrderCreateResponseDto.java | 5 +++-- .../dto/OrderItemListGetResponseDto.java | 3 +-- .../order/dto/OrderItemResponseDTO.java | 10 +++++++++ .../order/service/OrderService.java | 21 ++++++++++++------ .../repository/WaitingRedisRepository.java | 4 ++-- 7 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java new file mode 100644 index 00000000..74f77241 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java @@ -0,0 +1,22 @@ +package com.nowait.applicationadmin.reservation.repository; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class WaitingRedisRepository { + private final StringRedisTemplate redisTemplate; + + // 대기열 전체 인원수 조회 + public long getWaitingCountByStoreId(Long storeId) { + String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + Long count = redisTemplate.opsForZSet().zCard(key); + return count == null ? 0 : count; + } +} + diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 9bc3b061..ad216a56 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -14,7 +14,6 @@ import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto; -import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; import com.nowait.applicationuser.order.service.OrderService; import com.nowait.common.api.ApiUtils; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index a11a863d..87706232 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -2,6 +2,7 @@ import java.util.List; +import com.nowait.domaincorerdb.order.entity.OrderItem; import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.entity.OrderStatus; @@ -22,14 +23,14 @@ public class OrderCreateResponseDto { private OrderStatus status; private Integer totalPrice; - public static OrderCreateResponseDto fromEntity(UserOrder order) { + public static OrderCreateResponseDto fromEntity(UserOrder order,List items) { return OrderCreateResponseDto.builder() .orderId(order.getId()) .storeId(order.getStore().getStoreId()) .storeName(order.getStore().getName()) .sessionId(order.getSessionId()) .depositorName(order.getDepositorName()) - .orderItems(List.of()) + .orderItems(items.stream().map(OrderItemResponseDTO::fromEntity).toList()) .status(order.getStatus()) .totalPrice(order.getTotalPrice()) .build(); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java index 698dddaa..1bec7fd3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java @@ -18,9 +18,8 @@ public class OrderItemListGetResponseDto { private String menuName; private Integer quantity; private Integer price; - private OrderStatus status; - public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem,OrderStatus status) { + public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem) { return OrderItemListGetResponseDto.builder() .orderId(orderItem.getUserOrder().getId()) .menuName(orderItem.getMenu().getName()) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java index 1c29a4f2..f598fc96 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemResponseDTO.java @@ -1,5 +1,7 @@ package com.nowait.applicationuser.order.dto; +import com.nowait.domaincorerdb.order.entity.OrderItem; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -13,4 +15,12 @@ public class OrderItemResponseDTO { private Long menuId; // 메뉴 ID private String menuName; // 메뉴 이름 private int quantity; // 수량 + + public static OrderItemResponseDTO fromEntity(OrderItem orderItem) { + return OrderItemResponseDTO.builder() + .menuId(orderItem.getMenu().getId()) + .menuName(orderItem.getMenu().getName()) + .quantity(orderItem.getQuantity()) + .build(); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 5444749f..746cf9a8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -90,20 +90,24 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId, orderItemRepository.saveAll(orderItems); // 5. 응답 반환 - return OrderCreateResponseDto.fromEntity(savedOrder); + return OrderCreateResponseDto.fromEntity(savedOrder,orderItems); } @Transactional(readOnly = true) - public List getOrderItemsGroupByStatus(Long storeId, Long tableId, String sessionId) { + public List getOrderItemsGroupByStatus( + Long storeId, Long tableId, String sessionId) { List userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId); - // flatMap으로 OrderItem + status로 펼친 뒤, status별로 groupBy Map> grouped = userOrders.stream() - .flatMap(order -> order.getOrderItems().stream() - .map(orderItem -> OrderItemListGetResponseDto.fromEntity(orderItem, order.getStatus()))) - .collect(Collectors.groupingBy(OrderItemListGetResponseDto::getStatus)); + .flatMap(order -> order.getOrderItems().stream()) + .collect(Collectors.groupingBy( + orderItem -> orderItem.getUserOrder().getStatus(), + Collectors.mapping( + OrderItemListGetResponseDto::fromEntity, + Collectors.toList() + ) + )); - // status별로 responseDto로 변환 return grouped.entrySet().stream() .map(entry -> OrderItemGroupByStatusResponseDto.builder() .status(entry.getKey()) @@ -114,6 +118,9 @@ public List getOrderItemsGroupByStatus(Long s + + + private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { if (storeId == null || tableId == null || orderCreateRequestDto == null) { throw new OrderParameterEmptyException(); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java index 36f574db..490ba241 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java @@ -1,10 +1,10 @@ package com.nowait.applicationuser.reservation.repository; - -import java.util.Set; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; + import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + import lombok.RequiredArgsConstructor; @Repository From 13820e358222ba563dfffe360c7eda72a3d9c99c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 19 Jul 2025 01:41:31 +0900 Subject: [PATCH 249/685] =?UTF-8?q?refactor:=20setAllowCredentials=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/config/security/CorsConfig.java | 2 +- .../com/nowait/applicationuser/config/security/CorsConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java index c0c27282..7cb37fab 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java @@ -19,7 +19,7 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - // config.setAllowCredentials(true); // 쿠키 포함 허용 + config.setAllowCredentials(true); // 쿠키 포함 허용 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index 3390ff00..f437ea21 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -19,7 +19,7 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - // config.setAllowCredentials(true); // 쿠키 포함 허용 + config.setAllowCredentials(true); // 쿠키 포함 허용 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 From a0c5e4dfbea2e5974d5ac97f68acb3beb9f67588 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 21 Jul 2025 15:53:47 +0900 Subject: [PATCH 250/685] =?UTF-8?q?feat(Reservation):=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 병준님 요청 사항 기반 항목 추가 - DepartmentRepository 생성 - 내 모든 대기열 조회 로직 구현 --- .../order/dto/OrderCreateResponseDto.java | 2 +- .../controller/ReservationController.java | 14 +++++- .../reservation/dto/MyWaitingQueueDto.java | 38 ++++++++++++++++ .../repository/WaitingRedisRepository.java | 7 +++ .../service/ReservationService.java | 44 +++++++++++++++++++ .../repository/DepartmentRepository.java | 8 ++++ .../store/repository/StoreRepository.java | 5 +++ 7 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index 87706232..dcb675ab 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -3,8 +3,8 @@ import java.util.List; import com.nowait.domaincorerdb.order.entity.OrderItem; -import com.nowait.domaincorerdb.order.entity.UserOrder; import com.nowait.domaincorerdb.order.entity.OrderStatus; +import com.nowait.domaincorerdb.order.entity.UserOrder; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index eef6a538..70899d83 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -1,5 +1,7 @@ package com.nowait.applicationuser.reservation.controller; +import java.util.List; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -11,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; @@ -67,7 +70,7 @@ public ResponseEntity createQueue( } @GetMapping("/get/queue/redis/{storeId}") - @Operation(summary = "본인 대기열 조회", description = "특정 주점에 대한 본인 대기열 조회") + @Operation(summary = "특정 주점의 본인 대기열 조회", description = "특정 주점에 대한 본인 대기열 조회") @ApiResponse(responseCode = "200", description = "본인 대기열 조회") public ResponseEntity getQueue( @PathVariable Long storeId, @@ -98,4 +101,13 @@ public ResponseEntity deleteQueue( ) ); } + + @GetMapping("/my/waitings") + @Operation(summary = "내 모든 대기열 리스트 확인", description = "내가 신청한 모든 대기열 리스트 확인") + @ApiResponse(responseCode = "200", description = "대기열 리스트 조회") + public ResponseEntity getAllMyWaitings(@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + List response = reservationService.getAllMyWaitings(customOAuth2User); + return ResponseEntity.ok(ApiUtils.success(response)); + } + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java new file mode 100644 index 00000000..2190698b --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java @@ -0,0 +1,38 @@ +package com.nowait.applicationuser.reservation.dto; + +import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "내 대기 큐 정보 DTO") +public class MyWaitingQueueDto { + @Schema(description = "주점 ID", example = "1") + private Long storeId; + @Schema(description = "주점 이름", example = "비어파티") + private String storeName; + @Schema(description = "학과 이름", example = "경영학과") + private String departmentName; + @Schema(description = "대기 순번", example = "1") + private Integer rank; + @Schema(description = "내 앞의 대기 팀 수", example = "3") + private Integer teamsAhead; + @Schema(description = "대기 인원(파티 사이즈)", example = "4") + private Integer partySize; // 파티 인원 + @Schema(description = "대기 상태", example = "WAITING") + private String status; + @Schema(description = "대기 등록 일시", example = "2024-07-20T18:00:00") + private LocalDateTime registeredAt; + @Schema(description = "주점 위치", example = "학생회관 1층 104호") + private String location; + @Schema(description = "프로필 이미지 URL", example = "https://cdn.gtable.com/profile/user1.jpg") + private String profileImageUrl; +} + diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java index 490ba241..1502f815 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java @@ -47,6 +47,13 @@ public boolean removeWaiting(Long storeId, String userId) { redisTemplate.opsForHash().delete(partyKey, userId); return true; } + + public Long getWaitingTimestamp(Long storeId, String userId) { + String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + Double score = redisTemplate.opsForZSet().score(key, userId); + return score == null ? null : score.longValue(); + } + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index d3310e95..b735770d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -1,17 +1,22 @@ package com.nowait.applicationuser.reservation.service; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; @@ -34,6 +39,7 @@ public class ReservationService { private final StoreRepository storeRepository; private final UserRepository userRepository; private final WaitingRedisRepository waitingRedisRepository; + private final DepartmentRepository departmentRepository; public WaitingResponseDto registerWaiting( Long storeId,CustomOAuth2User customOAuth2User,ReservationCreateRequestDto requestDto @@ -92,6 +98,44 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { } return removed; } + //TODO 성능 개선 필요 + public List getAllMyWaitings(CustomOAuth2User customOAuth2User) { + String userId = customOAuth2User.getUserId().toString(); + + // storeId 전체 목록(운영환경에서는 DB나 캐싱에서 조회) + List allStoreIds = storeRepository.findAllActiveStoreIds(); // 예시, 필요시 직접 쿼리 정의 + + List result = new ArrayList<>(); + for (Long storeId : allStoreIds) { + Long rank = waitingRedisRepository.getRank(storeId, userId); + Store store = storeRepository.findById(storeId).orElseThrow(StoreNotFoundException::new); + Long timestamp = waitingRedisRepository.getWaitingTimestamp(storeId, userId); + LocalDateTime createdAt = null; + if (timestamp != null) { + createdAt = LocalDateTime.ofInstant( + Instant.ofEpochMilli(timestamp), + ZoneId.of("Asia/Seoul") + ); + } + if (rank != null) { // 해당 매장에 대기 중인 경우만 + Integer partySize = waitingRedisRepository.getPartySize(storeId, userId); + result.add(MyWaitingQueueDto.builder() + .storeId(storeId) + .storeName(store.getName()) + .departmentName(departmentRepository.findById(store.getDepartmentId()).get().getName()) + .rank(rank.intValue() + 1) + .teamsAhead(rank.intValue()) + .partySize(partySize == null ? 0 : partySize) + .status("WAITING") // TODO 구현 필요 + .registeredAt(createdAt) + .location(store.getLocation()) + .profileImageUrl(customOAuth2User.getUser().getProfileImage()) + .build()); + } + } + return result; + } + @Transactional public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User, diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java new file mode 100644 index 00000000..c2ee280b --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java @@ -0,0 +1,8 @@ +package com.nowait.domaincorerdb.department.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface DepartmentRepository extends JpaRepository { +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index efeec3ca..fbc0ebca 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import com.nowait.domaincorerdb.store.entity.Store; @@ -21,4 +22,8 @@ public interface StoreRepository extends JpaRepository { List findByNameContainingIgnoreCaseAndDeletedFalse(String name); Slice findAllByDeletedFalseOrderByStoreIdAsc(Pageable pageable); + + // TODO queryDSL으로 전환? + @Query("select s.storeId from Store s where s.isActive = true and s.deleted = false") + List findAllActiveStoreIds(); } From 10fb5fb11ed60f0b76821df5adc6ad15e8aa4fee Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 21 Jul 2025 16:25:56 +0900 Subject: [PATCH 251/685] =?UTF-8?q?feat(Reservation):=20=EB=82=B4=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EB=8C=80=EA=B8=B0=EC=97=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 성능 개선 로직 추가 --- .../repository/WaitingRedisRepository.java | 31 ++++++++++ .../service/ReservationService.java | 56 ++++++++++++------- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java index 1502f815..55fa40d6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java @@ -1,5 +1,10 @@ package com.nowait.applicationuser.reservation.repository; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; @@ -20,6 +25,9 @@ public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); if (Boolean.TRUE.equals(added)) { redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); + // TTL 12시간(43200초) 설정 + redisTemplate.expire(queueKey, Duration.ofHours(12)); + redisTemplate.expire(partyKey, Duration.ofHours(12)); } return Boolean.TRUE.equals(added); } @@ -54,6 +62,29 @@ public Long getWaitingTimestamp(Long storeId, String userId) { return score == null ? null : score.longValue(); } + // 사용자 대기중인 매장 목록 + public List getUserWaitingStoreIds(String userId) { + // key pattern으로 모든 매장 대기열 조회 (keys: waiting:*) + Set keys = redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*"); + if (keys == null) return List.of(); + + List result = new ArrayList<>(); + for (String key : keys) { + // ZSet만 필터링 + String type = redisTemplate.type(key).code(); + if (!"zset".equals(type)) { + continue; // hash 등은 스킵! + } + // waiting:{storeId}만 추출 (waiting:party:7 등은 통과 안 됨) + Long storeId = Long.valueOf(key.substring(key.lastIndexOf(":") + 1)); + if (redisTemplate.opsForZSet().rank(key, userId) != null) { + result.add(storeId); + } + } + return result; + } + + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index b735770d..5006757f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -5,6 +5,10 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,6 +20,7 @@ import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; @@ -101,33 +106,42 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { //TODO 성능 개선 필요 public List getAllMyWaitings(CustomOAuth2User customOAuth2User) { String userId = customOAuth2User.getUserId().toString(); - - // storeId 전체 목록(운영환경에서는 DB나 캐싱에서 조회) - List allStoreIds = storeRepository.findAllActiveStoreIds(); // 예시, 필요시 직접 쿼리 정의 + List userWaitingStoreIds = waitingRedisRepository.getUserWaitingStoreIds(userId); List result = new ArrayList<>(); - for (Long storeId : allStoreIds) { - Long rank = waitingRedisRepository.getRank(storeId, userId); - Store store = storeRepository.findById(storeId).orElseThrow(StoreNotFoundException::new); - Long timestamp = waitingRedisRepository.getWaitingTimestamp(storeId, userId); - LocalDateTime createdAt = null; - if (timestamp != null) { - createdAt = LocalDateTime.ofInstant( - Instant.ofEpochMilli(timestamp), - ZoneId.of("Asia/Seoul") - ); - } - if (rank != null) { // 해당 매장에 대기 중인 경우만 + if (!userWaitingStoreIds.isEmpty()) { + // Store, Department 배치 조회 + List stores = storeRepository.findAllById(userWaitingStoreIds); + Map storeMap = stores.stream() + .collect(Collectors.toMap(Store::getStoreId, Function.identity())); + + Set departmentIds = stores.stream() + .map(Store::getDepartmentId) + .collect(Collectors.toSet()); + Map departmentNameMap = departmentRepository.findAllById(departmentIds).stream() + .collect(Collectors.toMap(Department::getId, Department::getName)); + + for (Long storeId : userWaitingStoreIds) { + Store store = storeMap.get(storeId); + if (store == null) continue; + + Long rank = waitingRedisRepository.getRank(storeId, userId); Integer partySize = waitingRedisRepository.getPartySize(storeId, userId); + Long timestamp = waitingRedisRepository.getWaitingTimestamp(storeId, userId); + + LocalDateTime registeredAt = timestamp != null + ? LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Seoul")) + : null; + result.add(MyWaitingQueueDto.builder() .storeId(storeId) .storeName(store.getName()) - .departmentName(departmentRepository.findById(store.getDepartmentId()).get().getName()) - .rank(rank.intValue() + 1) - .teamsAhead(rank.intValue()) - .partySize(partySize == null ? 0 : partySize) - .status("WAITING") // TODO 구현 필요 - .registeredAt(createdAt) + .departmentName(departmentNameMap.get(store.getDepartmentId())) + .rank(rank != null ? rank.intValue() + 1 : 0) + .teamsAhead(rank != null ? rank.intValue() : 0) + .partySize(partySize != null ? partySize : 0) + .status("WAITING") // 필요시 redis에 상태값이 있으면 조회해서 세팅 + .registeredAt(registeredAt) .location(store.getLocation()) .profileImageUrl(customOAuth2User.getUser().getProfileImage()) .build()); From 600c24d30d904c7578a2d8b0959bcecb9f5a25c2 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 21 Jul 2025 17:52:41 +0900 Subject: [PATCH 252/685] =?UTF-8?q?feat(Reservation):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=98=88=EC=95=BD=20=EB=8C=80=EA=B8=B0=EC=97=B4=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - redis에 예약 상태 key 추가 - 특정 userId 호출 로직 구현 - 예약 상태 CALLING으로 변경 시, TTL 10분으로 변경 --- .../controller/ReservationController.java | 18 ++++++++++ .../dto/CallingWaitingResponseDto.java | 35 +++++++++++++++++++ .../repository/WaitingRedisRepository.java | 23 ++++++++++++ .../service/ReservationService.java | 26 +++++++++++++- ...y.java => WaitingUserRedisRepository.java} | 27 +++++++------- .../service/ReservationService.java | 24 ++++++------- .../common/util/RedisKeyUtils.java | 2 ++ 7 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallingWaitingResponseDto.java rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/{WaitingRedisRepository.java => WaitingUserRedisRepository.java} (80%) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index 9868d04d..127a14e4 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -8,9 +8,11 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; +import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; import com.nowait.applicationadmin.reservation.service.ReservationService; @@ -61,5 +63,21 @@ public ResponseEntity updateReservationStatus(@PathVariable Long reservationI ); } + @PatchMapping("/admin/calling/redis/{storeId}") + @Operation(summary = "예약팀 호출", description = "특정 예약에 대한 호출 진행(호출하는 순간 10분 타임어택)") + @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : WAITING -> CALLING") + public ResponseEntity callWaiting(@PathVariable Long storeId, + @RequestParam String userId, + @AuthenticationPrincipal MemberDetails memberDetails) { + CallingWaitingResponseDto response = reservationService.callWaiting(storeId, userId, memberDetails); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + ); + } + } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallingWaitingResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallingWaitingResponseDto.java new file mode 100644 index 00000000..2a995b84 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallingWaitingResponseDto.java @@ -0,0 +1,35 @@ +package com.nowait.applicationadmin.reservation.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "관리자 호출(호출 상태 변경) 응답 DTO") +public class CallingWaitingResponseDto { + @Schema(description = "매장 ID", example = "7") + private Long storeId; + + @Schema(description = "유저 ID", example = "123") + private String userId; + + @Schema(description = "대기 상태", example = "CALLING") + private String status; + + @Schema(description = "호출 시각", example = "2025-07-21T17:01:00") + private LocalDateTime calledAt; + + @Schema(description = "대기 순번", example = "3") + private Integer rank; + + @Schema(description = "파티 인원", example = "4") + private Integer partySize; + + +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java index 74f77241..21f6e5c6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.reservation.repository; +import java.time.Duration; + import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; @@ -18,5 +20,26 @@ public long getWaitingCountByStoreId(Long storeId) { Long count = redisTemplate.opsForZSet().zCard(key); return count == null ? 0 : count; } + + // 상태값 저장 및 변경 + public void setWaitingStatus(Long storeId, String userId, String status) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + redisTemplate.opsForHash().put(statusKey, userId, status); + // WAITING -> CALLING 으로 변경 시 TTL 12h에서 10m로 변경 + if (status.equals("CALLING")) { + redisTemplate.expire(queueKey, Duration.ofMinutes(10)); + redisTemplate.expire(partyKey, Duration.ofMinutes(10)); + redisTemplate.expire(statusKey, Duration.ofMinutes(10)); + } + } + + // 상태값 조회 + public String getWaitingStatus(Long storeId, String userId) { + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + Object value = redisTemplate.opsForHash().get(statusKey, userId); + return value == null ? null : value.toString(); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index cf61f493..8b23c2df 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.reservation.service; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -7,9 +8,11 @@ import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; +import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; +import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; @@ -31,6 +34,7 @@ public class ReservationService { private final ReservationRepository reservationRepository; private final UserRepository userRepository; + private final WaitingRedisRepository waitingRedisRepository; @Transactional(readOnly = true) public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { @@ -73,7 +77,27 @@ public CallGetResponseDto updateReservationStatus(Long reservationId, Reservatio reservation.updateStatus(requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } - + @Transactional + public CallingWaitingResponseDto callWaiting(Long storeId, String userId, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new ReservationViewUnauthorizedException(); + } + String status = waitingRedisRepository.getWaitingStatus(storeId, userId); + System.out.println(status); + if (!"WAITING".equals(status)) { + throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); + } + waitingRedisRepository.setWaitingStatus(storeId, userId, "CALLING"); + LocalDateTime calledAt = LocalDateTime.now(); + return CallingWaitingResponseDto.builder() + .storeId(storeId) + .userId(userId) + .status("CALLING") + .calledAt(calledAt) + .build(); + } + //TODO CALLING -> 입장완료 처리 로직 구현 필요(redis에서 삭제 후 RDB에 저장하는게 나을지??) } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java similarity index 80% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 55fa40d6..d1704c61 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -14,13 +14,15 @@ @Repository @RequiredArgsConstructor -public class WaitingRedisRepository { +public class WaitingUserRedisRepository { private final StringRedisTemplate redisTemplate; // 중복 등록 방지: 이미 있으면 추가X + // 특정 주점에 대한 예약 등록 public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, long timestamp) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); if (Boolean.TRUE.equals(added)) { @@ -28,26 +30,22 @@ public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, // TTL 12시간(43200초) 설정 redisTemplate.expire(queueKey, Duration.ofHours(12)); redisTemplate.expire(partyKey, Duration.ofHours(12)); + redisTemplate.expire(statusKey, Duration.ofHours(12)); } return Boolean.TRUE.equals(added); } - + // 예약한 사람이 등록한 동반인원(partySize) 조회 public Integer getPartySize(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; Object value = redisTemplate.opsForHash().get(partyKey, userId); return Integer.valueOf(value.toString()); } - + // 예약자 대기순위 조회 public Long getRank(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; return redisTemplate.opsForZSet().rank(key, userId); } - - public Long getWaitingCount(Long storeId) { - String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; - return redisTemplate.opsForZSet().zCard(key); - } - + // 예약 취소 public boolean removeWaiting(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; @@ -55,14 +53,14 @@ public boolean removeWaiting(Long storeId, String userId) { redisTemplate.opsForHash().delete(partyKey, userId); return true; } - + // 예약 등록 시간 public Long getWaitingTimestamp(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; Double score = redisTemplate.opsForZSet().score(key, userId); return score == null ? null : score.longValue(); } - // 사용자 대기중인 매장 목록 + // 사용자가 대기중인 전체 매장 목록 조회 public List getUserWaitingStoreIds(String userId) { // key pattern으로 모든 매장 대기열 조회 (keys: waiting:*) Set keys = redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*"); @@ -83,7 +81,12 @@ public List getUserWaitingStoreIds(String userId) { } return result; } - + // 상태값 조회 + public String getWaitingStatus(Long storeId, String userId) { + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + Object value = redisTemplate.opsForHash().get(statusKey, userId); + return value == null ? null : value.toString(); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 5006757f..959ac887 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -17,7 +17,7 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; -import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository; +import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.department.entity.Department; @@ -43,7 +43,7 @@ public class ReservationService { private final ReservationRepository reservationRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; - private final WaitingRedisRepository waitingRedisRepository; + private final WaitingUserRedisRepository waitingUserRedisRepository; private final DepartmentRepository departmentRepository; public WaitingResponseDto registerWaiting( @@ -65,12 +65,12 @@ public WaitingResponseDto registerWaiting( long timestamp = System.currentTimeMillis(); // 예약 신청 유저 큐(queue)에 추가 - boolean added = waitingRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), timestamp); + boolean added = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), timestamp); if (!added) { throw new IllegalArgumentException("Failed to add to waiting queue"); } // 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환 - Long rank = waitingRedisRepository.getRank(storeId, userId); + Long rank = waitingUserRedisRepository.getRank(storeId, userId); return WaitingResponseDto.builder() .rank(rank == null ? -1 : rank.intValue() + 1) .partySize(requestDto.getPartySize() == null ? 0 : requestDto.getPartySize()) @@ -83,8 +83,8 @@ public WaitingResponseDto myWaitingInfo(Long storeId, CustomOAuth2User customOAu if (storeId == null || userId.trim().isEmpty()) { throw new IllegalArgumentException("Invalid storeId or userId"); } - Long rank = waitingRedisRepository.getRank(storeId, userId); - Integer partySize = waitingRedisRepository.getPartySize(storeId, userId); + Long rank = waitingUserRedisRepository.getRank(storeId, userId); + Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); return WaitingResponseDto.builder() .rank(rank == null ? -1 : rank.intValue() + 1) .partySize(partySize == null ? 0 : partySize) @@ -97,7 +97,7 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { throw new IllegalArgumentException("Invalid storeId or userId"); } // 대기열에서 제거 및 결과 반환 - boolean removed = waitingRedisRepository.removeWaiting(storeId, userId); + boolean removed = waitingUserRedisRepository.removeWaiting(storeId, userId); if (!removed) { throw new IllegalArgumentException("Waiting not found"); } @@ -106,7 +106,7 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { //TODO 성능 개선 필요 public List getAllMyWaitings(CustomOAuth2User customOAuth2User) { String userId = customOAuth2User.getUserId().toString(); - List userWaitingStoreIds = waitingRedisRepository.getUserWaitingStoreIds(userId); + List userWaitingStoreIds = waitingUserRedisRepository.getUserWaitingStoreIds(userId); List result = new ArrayList<>(); if (!userWaitingStoreIds.isEmpty()) { @@ -125,9 +125,9 @@ public List getAllMyWaitings(CustomOAuth2User customOAuth2Use Store store = storeMap.get(storeId); if (store == null) continue; - Long rank = waitingRedisRepository.getRank(storeId, userId); - Integer partySize = waitingRedisRepository.getPartySize(storeId, userId); - Long timestamp = waitingRedisRepository.getWaitingTimestamp(storeId, userId); + Long rank = waitingUserRedisRepository.getRank(storeId, userId); + Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); + Long timestamp = waitingUserRedisRepository.getWaitingTimestamp(storeId, userId); LocalDateTime registeredAt = timestamp != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Seoul")) @@ -140,7 +140,7 @@ public List getAllMyWaitings(CustomOAuth2User customOAuth2Use .rank(rank != null ? rank.intValue() + 1 : 0) .teamsAhead(rank != null ? rank.intValue() : 0) .partySize(partySize != null ? partySize : 0) - .status("WAITING") // 필요시 redis에 상태값이 있으면 조회해서 세팅 + .status(waitingUserRedisRepository.getWaitingStatus(storeId, userId)) // 필요시 redis에 상태값이 있으면 조회해서 세팅 .registeredAt(registeredAt) .location(store.getLocation()) .profileImageUrl(customOAuth2User.getUser().getProfileImage()) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index 272b029f..af6a9ed1 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -16,6 +16,7 @@ public class RedisKeyUtils { // Waiting keys private static final String WAITING_KEY_PREFIX = "waiting:"; private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:"; + private static final String WAITING_STATUS_KEY_PREFIX = "waiting:status:"; private RedisKeyUtils() { @@ -40,4 +41,5 @@ public static String buildNextKey() { public static String buildWaitingKeyPrefix() { return WAITING_KEY_PREFIX; } public static String buildWaitingPartySizeKeyPrefix() { return WAITING_PARTYSIZE_KEY_PREFIX; } + public static String buildWaitingStatusKeyPrefix() { return WAITING_STATUS_KEY_PREFIX; } } From a99ce8976bbbf13122c57d153b45a29d6533e9de Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Mon, 21 Jul 2025 18:01:33 +0900 Subject: [PATCH 253/685] =?UTF-8?q?feat(Reservation):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=98=88=EC=95=BD=20=EB=8C=80=EA=B8=B0=EC=97=B4=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - redis에 예약 상태 key 추가 - 특정 userId 호출 로직 구현 - 예약 상태 CALLING으로 변경 시, TTL 10분으로 변경 --- .../reservation/repository/WaitingRedisRepository.java | 2 +- .../reservation/service/ReservationService.java | 2 -- .../reservation/repository/WaitingUserRedisRepository.java | 3 +++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java index 21f6e5c6..8faddbe9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java @@ -28,7 +28,7 @@ public void setWaitingStatus(Long storeId, String userId, String status) { String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForHash().put(statusKey, userId, status); // WAITING -> CALLING 으로 변경 시 TTL 12h에서 10m로 변경 - if (status.equals("CALLING")) { + if ("CALLING".equals(status)) { redisTemplate.expire(queueKey, Duration.ofMinutes(10)); redisTemplate.expire(partyKey, Duration.ofMinutes(10)); redisTemplate.expire(statusKey, Duration.ofMinutes(10)); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 8b23c2df..f8170199 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -77,14 +77,12 @@ public CallGetResponseDto updateReservationStatus(Long reservationId, Reservatio reservation.updateStatus(requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } - @Transactional public CallingWaitingResponseDto callWaiting(Long storeId, String userId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new ReservationViewUnauthorizedException(); } String status = waitingRedisRepository.getWaitingStatus(storeId, userId); - System.out.println(status); if (!"WAITING".equals(status)) { throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index d1704c61..2b74fd1d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -27,6 +27,7 @@ public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); if (Boolean.TRUE.equals(added)) { redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); + redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); // TTL 12시간(43200초) 설정 redisTemplate.expire(queueKey, Duration.ofHours(12)); redisTemplate.expire(partyKey, Duration.ofHours(12)); @@ -49,8 +50,10 @@ public Long getRank(Long storeId, String userId) { public boolean removeWaiting(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForZSet().remove(key, userId); redisTemplate.opsForHash().delete(partyKey, userId); + redisTemplate.opsForHash().delete(statusKey, userId); return true; } // 예약 등록 시간 From 92662cc514b4fee4be26752cc3e5f6d488e49ca5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:18:56 +0900 Subject: [PATCH 254/685] =?UTF-8?q?chore:=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index f437ea21..97b483c9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173","http://localhost:63342")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From ab1b4fa6741ab89af50ad0e3437f7f144c46ca09 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:19:18 +0900 Subject: [PATCH 255/685] =?UTF-8?q?feat(Department):=20DepartmentRepositor?= =?UTF-8?q?y=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../department/repository/DepartmentRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java new file mode 100644 index 00000000..023d2409 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.department.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.nowait.domaincorerdb.department.entity.Department; + +@Repository +public interface DepartmentRepository extends JpaRepository { +} From ee2d193f95ef7316be90b62d05c3e5c5319d646b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:19:38 +0900 Subject: [PATCH 256/685] =?UTF-8?q?feat(Department):=20DepartmentName=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/StoreDepartmentReadResponse.java | 23 ++++++++ .../store/dto/StorePageReadDto.java | 54 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java new file mode 100644 index 00000000..fe04584f --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java @@ -0,0 +1,23 @@ +package com.nowait.applicationuser.store.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreDepartmentReadResponse { + + private List storePageReadDtos; + private boolean hasNext; + + public static StoreDepartmentReadResponse of(List storePageReadDtos, boolean hasNext) { + return StoreDepartmentReadResponse.builder() + .storePageReadDtos(storePageReadDtos) + .hasNext(hasNext) + .build(); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java new file mode 100644 index 00000000..050cc091 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java @@ -0,0 +1,54 @@ +package com.nowait.applicationuser.store.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.nowait.domaincorerdb.store.entity.ImageType; +import com.nowait.domaincorerdb.store.entity.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StorePageReadDto { + private Long storeId; + private Long departmentId; + private String departmentName; + private String name; + private String location; + private String description; + private StoreImageUploadResponse profileImage; + private List bannerImages; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + + public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + + return StorePageReadDto.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .departmentId(store.getDepartmentId()) + .departmentName(departmentName) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .profileImage(profile) + .bannerImages(banners) + .build(); + } +} From 4ad97a354e094d1558a61790ac0af17765f2f826 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:20:29 +0900 Subject: [PATCH 257/685] =?UTF-8?q?feat(Department):=20=EC=A3=BC=EC=A0=90?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index f1dbc8d2..b39a53a8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -46,6 +46,22 @@ public ResponseEntity getAllStores() { description = "모든 주점을 페이지네이션으로 조회합니다." ) @ApiResponse(responseCode = "200", description = "모든 주점 페이지네이션 조회 성공") + public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable) { + return ResponseEntity + .ok() + .body( + ApiUtils.success( + storeService.getAllStoresByPageAndDeparments(pageable) + ) + ); + } + + @GetMapping("/low-wait/infinite-scroll") + @Operation( + summary = "모든 주점 페이지네이션 조회", + description = "모든 주점을 페이지네이션으로 조회합니다." + ) + @ApiResponse(responseCode = "200", description = "모든 주점 페이지네이션 조회 성공") public ResponseEntity getAllStores(Pageable pageable) { return ResponseEntity .ok() From a8f91ce77bb4cc960b3c8957d56d014995739f59 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:20:46 +0900 Subject: [PATCH 258/685] =?UTF-8?q?feat(Department):=20findByStore=5FStore?= =?UTF-8?q?IdIn=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/store/repository/StoreImageRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java index 1ad6929e..45639941 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java @@ -15,5 +15,7 @@ public interface StoreImageRepository extends JpaRepository { List findByStore(Store store); + List findByStore_StoreIdIn(List storeIds); + Optional findByStoreStoreIdAndImageType(Long storeId, ImageType imageType); } From 72f5aec04b9c8acd039bad97369eb522387b6a3d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:21:10 +0900 Subject: [PATCH 259/685] =?UTF-8?q?feat(StoreImage):=20StoreImageUploadRes?= =?UTF-8?q?ponse=20storeId=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/dto/StoreImageUploadResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java index 62822b3d..39dbf2a0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreImageUploadResponse.java @@ -10,12 +10,14 @@ @Builder public class StoreImageUploadResponse { private final Long id; + private final Long storeId; private final String imageUrl; private final ImageType imageType; public static StoreImageUploadResponse fromEntity(StoreImage storeImage) { return StoreImageUploadResponse.builder() .id(storeImage.getId()) + .storeId(storeImage.getStore().getStoreId()) .imageUrl(storeImage.getImageUrl()) .imageType(storeImage.getImageType()) .build(); From 717cf65e8853ff88016e27e49e72bb1f8ae49e38 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:25:33 +0900 Subject: [PATCH 260/685] =?UTF-8?q?feat(Store):=20=EC=A3=BC=EC=A0=90=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20N+1=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreService.java | 6 +- .../store/service/StoreServiceImpl.java | 96 +++++++++++++++++-- 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index f8c854ad..f8bf540a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -4,6 +4,8 @@ import org.springframework.data.domain.Pageable; +import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; +import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StoreReadDto; import com.nowait.applicationuser.store.dto.StoreReadResponse; @@ -13,8 +15,10 @@ public interface StoreService { StoreReadResponse getAllStoresByPage(Pageable pageable); + StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable); + StoreReadDto getStoreByStoreId(Long storeId); - List searchStoresByName(String name); + List searchStoresByName(String name); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 39e94c03..566e91ea 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -1,15 +1,21 @@ package com.nowait.applicationuser.store.service; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; import com.nowait.applicationuser.store.dto.StoreImageUploadResponse; +import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StoreReadDto; import com.nowait.applicationuser.store.dto.StoreReadResponse; +import com.nowait.domaincorerdb.department.entity.Department; +import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; @@ -25,6 +31,7 @@ public class StoreServiceImpl implements StoreService { private final StoreRepository storeRepository; private final StoreImageRepository storeImageRepository; + private final DepartmentRepository departmentRepository; @Override @@ -67,6 +74,55 @@ public StoreReadResponse getAllStoresByPage(Pageable pageable) { return StoreReadResponse.of(storeRead, hasNext); } + @Override + @Transactional(readOnly = true) + public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable) { + // 1) 페이징된 Store 스냅샷 조회 + Slice slice = storeRepository.findAllByDeletedFalseOrderByStoreIdAsc(pageable); + List stores = slice.getContent(); + + // 2) 각 StoreId / Department ID 추출 + List storeIds = stores.stream() + .map(Store::getStoreId) + .toList(); + List deptIds = stores.stream() + .map(Store::getDepartmentId) + .distinct() + .toList(); + + // 3) 각 StoreId에 해당하는 이미지 조회 + List allImages = storeImageRepository.findByStore_StoreIdIn(storeIds); + Map> imageMap = allImages.stream() + .map(StoreImageUploadResponse::fromEntity) + .collect(Collectors.groupingBy( + StoreImageUploadResponse::getStoreId + )); + + + // 4) 각 DepartmentId에 해당하는 이름 조회 + List allDepts = departmentRepository.findAllById(deptIds); + Map deptNameMap = allDepts.stream() + .collect(Collectors.toMap( + Department::getId, + Department::getName + )); + + // 5) Dto 매핑 + List content = stores.stream() + .map(store -> { + List imgs = imageMap + .getOrDefault(store.getStoreId(), List.of()); + String departmentName = deptNameMap + .getOrDefault(store.getDepartmentId(), "Unknown Department"); + return StorePageReadDto.fromEntity(store, imgs, departmentName); + }) + .toList(); + + boolean hasNext = slice.hasNext(); + + return StoreDepartmentReadResponse.of(content, hasNext); + } + @Override @Transactional(readOnly = true) public StoreReadDto getStoreByStoreId(Long storeId) { @@ -84,19 +140,47 @@ public StoreReadDto getStoreByStoreId(Long storeId) { } @Override - public List searchStoresByName(String name) { + public List searchStoresByName(String name) { if (name == null || name.isBlank()) { throw new StoreParamEmptyException(); } + // 1) 페이징된 Store 스냅샷 조회 List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(name); + + // 2) 각 StoreId / Department ID 추출 + List storeIds = stores.stream() + .map(Store::getStoreId) + .toList(); + List deptIds = stores.stream() + .map(Store::getDepartmentId) + .distinct() + .toList(); + + // 3) 각 StoreId에 해당하는 이미지 조회 + List allImages = storeImageRepository.findByStore_StoreIdIn(storeIds); + Map> imageMap = allImages.stream() + .map(StoreImageUploadResponse::fromEntity) + .collect(Collectors.groupingBy( + StoreImageUploadResponse::getStoreId + )); + + // 4) 각 DepartmentId에 해당하는 이름 조회 + List allDepts = departmentRepository.findAllById(deptIds); + Map deptNameMap = allDepts.stream() + .collect(Collectors.toMap( + Department::getId, + Department::getName + )); + + // 5) Dto 매핑 return stores.stream() .map(store -> { - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - return StoreReadDto.fromEntity(store, imageDto); + List imgs = imageMap + .getOrDefault(store.getStoreId(), List.of()); + String departmentName = deptNameMap + .getOrDefault(store.getDepartmentId(), "Unknown Department"); + return StorePageReadDto.fromEntity(store, imgs, departmentName); }) .toList(); } From 88c664526bc39a9d6ad3c6ab0e32cd2ed344aa05 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 15:56:44 +0900 Subject: [PATCH 261/685] =?UTF-8?q?feat(Store):=20getStoreByStoreId=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=ED=83=80=EC=9E=85=20StorePageReadDto?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/service/StoreService.java | 2 +- .../applicationuser/store/service/StoreServiceImpl.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index f8bf540a..3df99912 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -17,7 +17,7 @@ public interface StoreService { StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable); - StoreReadDto getStoreByStoreId(Long storeId); + StorePageReadDto getStoreByStoreId(Long storeId); List searchStoresByName(String name); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 566e91ea..8b9a5a5e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -125,18 +125,22 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Override @Transactional(readOnly = true) - public StoreReadDto getStoreByStoreId(Long storeId) { + public StorePageReadDto getStoreByStoreId(Long storeId) { if (storeId == null) throw new StoreParamEmptyException(); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); + String departmentName = departmentRepository.findById(store.getDepartmentId()) + .map(Department::getName) + .orElse("Unknown Department"); + List images = storeImageRepository.findByStore(store); List imageDto = images.stream() .map(StoreImageUploadResponse::fromEntity) .toList(); - return StoreReadDto.fromEntity(store, imageDto); + return StorePageReadDto.fromEntity(store, imageDto, departmentName); } @Override From c66487fe275478be0da4e068d43e7cea57bc8d81 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 16:41:14 +0900 Subject: [PATCH 262/685] =?UTF-8?q?feat(Order):=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20N=20+=201=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/domaincorerdb/order/repository/OrderRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index b6b0a12d..01605efc 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -14,5 +15,6 @@ public interface OrderRepository extends JpaRepository { List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); + @EntityGraph(attributePaths = {"orderItems", "orderItems.menu"}) List findAllByStore_StoreId(Long storeId); } From 14e25722122f1648c30213aaf852b696ffb3085d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 17:49:39 +0900 Subject: [PATCH 263/685] =?UTF-8?q?refactor(rank):=20=EB=A7=A4=EC=B6=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20redis=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/scheduler/RankingRefreshScheduler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index ed81d051..30bcc934 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -60,6 +60,10 @@ private void doRefresh() { if (salesList.isEmpty()) { log.warn("매출 데이터가 없습니다. 다음 스냅샷 키를 초기화합니다."); + redis.delete(currentKey); + redis.delete(previousKey); + redis.delete(nextKey); + return; } From bcbc6fb103735df727bdbac8647c5a26007d6292 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 18:29:53 +0900 Subject: [PATCH 264/685] =?UTF-8?q?refactor(rank):=20=EB=A7=A4=EC=B6=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20redis=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/scheduler/RankingRefreshScheduler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index 30bcc934..9d9920be 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -35,7 +35,7 @@ public void init() { refresh(); } - @Scheduled(cron = "0 */5 * * * *") // 매 5분마다 실행 + @Scheduled(cron = "*/30 * * * * *") // 매 5분마다 실행 public void refresh() { log.info("RankingRefreshScheduler.refresh() called at {}", LocalDateTime.now()); @@ -62,7 +62,6 @@ private void doRefresh() { log.warn("매출 데이터가 없습니다. 다음 스냅샷 키를 초기화합니다."); redis.delete(currentKey); redis.delete(previousKey); - redis.delete(nextKey); return; } From 572b6c033fa9585235c44eed7cc6ee2d289c490c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 18:43:44 +0900 Subject: [PATCH 265/685] =?UTF-8?q?refactor(rank):=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A5=B4=EB=9F=AC=20=EC=8B=9C=EA=B0=84=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/scheduler/RankingRefreshScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java index 9d9920be..f99c4c09 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java @@ -35,7 +35,7 @@ public void init() { refresh(); } - @Scheduled(cron = "*/30 * * * * *") // 매 5분마다 실행 + @Scheduled(cron = "0 */5 * * * *") // 매 5분마다 실행 public void refresh() { log.info("RankingRefreshScheduler.refresh() called at {}", LocalDateTime.now()); From 8bd8de564a6798dd1c68a2291c98cccfa07961b1 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 22 Jul 2025 18:48:47 +0900 Subject: [PATCH 266/685] =?UTF-8?q?feat(Order,Reservation):=20=EC=A3=BC?= =?UTF-8?q?=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=EC=97=B4=20CRUD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order 조회 응답 데이터 구조 변경 - 관리자 측 대기열 조회,변경 로직 구현 --- .../controller/ReservationController.java | 73 +++++++------- .../reservation/dto/WaitingUserResponse.java | 31 ++++++ .../repository/WaitingRedisRepository.java | 47 ++++++--- .../service/ReservationService.java | 97 ++++++++++++++++++- .../order/controller/OrderController.java | 4 +- .../order/dto/OrderCreateResponseDto.java | 3 + ...tGetResponseDto.java => OrderMenuDto.java} | 13 +-- ...ResponseDto.java => OrderResponseDto.java} | 8 +- .../order/service/OrderService.java | 31 +++--- .../reservation/entity/Reservation.java | 2 +- .../repository/ReservationRepository.java | 5 + 11 files changed, 233 insertions(+), 81 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/{OrderItemListGetResponseDto.java => OrderMenuDto.java} (62%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/{OrderItemGroupByStatusResponseDto.java => OrderResponseDto.java} (67%) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index 127a14e4..a6871ed0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -1,22 +1,22 @@ package com.nowait.applicationadmin.reservation.controller; +import java.util.List; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; -import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; -import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; +import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.applicationadmin.reservation.service.ReservationService; import com.nowait.common.api.ApiUtils; +import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; @@ -32,42 +32,30 @@ public class ReservationController { private final ReservationService reservationService; - @GetMapping("/admin/{storeId}") - @Operation(summary = "주점별 예약리스트 조회", description = "특정 주점에 대한 예약리스트 조회") - @ApiResponse(responseCode = "200", description = "예약리스트 조회") - public ResponseEntity getReservationListByStoreId(@PathVariable Long storeId, - @AuthenticationPrincipal MemberDetails memberDetails) { - ReservationStatusSummaryDto response = reservationService.getReservationListByStoreId(storeId,memberDetails); - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - response - ) - ); + @GetMapping("/admin/{storeId}/waiting/users") + @Operation(summary = "주점별 전체 대기 리스트 조회", description = "주점에 대한 대기 리스트 조회(WAITING,CALLING)") + @ApiResponse(responseCode = "200", description = "주점별 전체 대기 리스트 조회") + public ResponseEntity> getWaitingUsersWithScore(@PathVariable Long storeId) { + List response = reservationService.getAllWaitingUserDetails(storeId); + return ResponseEntity.ok(response); } - @PatchMapping("/admin/updates/{reservationId}") - @Operation(summary = "예약팀 상태 변경", description = "특정 예약에 대한 상태 변경(예약중->호출중,호출중->입장완료,취소)") - @ApiResponse(responseCode = "200", description = "예약팀 상태 변경") - public ResponseEntity updateReservationStatus(@PathVariable Long reservationId, - @RequestBody ReservationStatusUpdateRequestDto requestDto, - @AuthenticationPrincipal MemberDetails memberDetails) { - CallGetResponseDto response = reservationService.updateReservationStatus(reservationId,requestDto,memberDetails); - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - response - ) - ); + @GetMapping("/admin/{storeId}/completed") + @Operation(summary = "주점별 전체 완료 리스트 조회", description = "주점에 대한 완료/취소 리스트 조회(CANCELED,CONFIRMED)") + @ApiResponse(responseCode = "200", description = "주점별 전체 완료/취소 리스트 조회") + public ResponseEntity getCompletedReservationList( + @PathVariable Long storeId, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + List response = reservationService.getCompletedWaitingUserDetails(storeId); + return ResponseEntity.ok(response); } - @PatchMapping("/admin/calling/redis/{storeId}") + @PatchMapping("/admin/{storeId}/call/{userId}") @Operation(summary = "예약팀 호출", description = "특정 예약에 대한 호출 진행(호출하는 순간 10분 타임어택)") @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : WAITING -> CALLING") public ResponseEntity callWaiting(@PathVariable Long storeId, - @RequestParam String userId, + @PathVariable String userId, @AuthenticationPrincipal MemberDetails memberDetails) { CallingWaitingResponseDto response = reservationService.callWaiting(storeId, userId, memberDetails); return ResponseEntity @@ -78,6 +66,23 @@ public ResponseEntity callWaiting(@PathVariable Long storeId, ) ); } + @PostMapping("/admin/update/{storeId}/{userId}/{status}") + @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") + @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") + public ResponseEntity updateEntry( + @PathVariable Long storeId, + @PathVariable String userId, + @PathVariable ReservationStatus status, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + String response = reservationService.processEntryStatus(storeId, userId, memberDetails,status); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + )); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java new file mode 100644 index 00000000..982e821c --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -0,0 +1,31 @@ +package com.nowait.applicationadmin.reservation.dto; + +import java.time.LocalDateTime; + +import com.nowait.domaincorerdb.reservation.entity.Reservation; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class WaitingUserResponse { + private String id; // userId + private Integer partySize; + private String userName; + private LocalDateTime createdAt; + private String status; + private Double score; // (필요시, 대기열 정렬 등) + + public static WaitingUserResponse fromEntity(Reservation reservation) { + return WaitingUserResponse.builder() + .id(reservation.getId().toString()) + .partySize(reservation.getPartySize()) + .userName(reservation.getUser().getNickname()) + .createdAt(reservation.getRequestedAt()) + .status(reservation.getStatus().name()) + .build(); + } +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java index 8faddbe9..f8610e65 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java @@ -1,8 +1,12 @@ package com.nowait.applicationadmin.reservation.repository; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Repository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; @@ -15,24 +19,25 @@ public class WaitingRedisRepository { private final StringRedisTemplate redisTemplate; // 대기열 전체 인원수 조회 - public long getWaitingCountByStoreId(Long storeId) { - String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; - Long count = redisTemplate.opsForZSet().zCard(key); - return count == null ? 0 : count; + public List> getAllWaitingWithScore(Long storeId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + Set> waitingSet = redisTemplate.opsForZSet().rangeWithScores(queueKey, 0, -1); + return waitingSet == null ? List.of() : new ArrayList<>(waitingSet); + } + + + public List getAllWaitingUserIds(Long storeId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + // 0부터 -1까지: 전체 범위 + Set userIds = redisTemplate.opsForZSet().range(queueKey, 0, -1); + return userIds == null ? List.of() : new ArrayList<>(userIds); } + // 상태값 저장 및 변경 public void setWaitingStatus(Long storeId, String userId, String status) { - String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; - String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForHash().put(statusKey, userId, status); - // WAITING -> CALLING 으로 변경 시 TTL 12h에서 10m로 변경 - if ("CALLING".equals(status)) { - redisTemplate.expire(queueKey, Duration.ofMinutes(10)); - redisTemplate.expire(partyKey, Duration.ofMinutes(10)); - redisTemplate.expire(statusKey, Duration.ofMinutes(10)); - } } // 상태값 조회 @@ -41,5 +46,23 @@ public String getWaitingStatus(Long storeId, String userId) { Object value = redisTemplate.opsForHash().get(statusKey, userId); return value == null ? null : value.toString(); } + + // partySize 조회 + public Integer getWaitingPartySize(Long storeId, String userId) { + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + Object value = redisTemplate.opsForHash().get(partyKey, userId); + return value == null ? null : Integer.valueOf(value.toString()); + } + + public void deleteWaiting(Long storeId, String userId) { + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + redisTemplate.opsForHash().delete(statusKey, userId); + + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + redisTemplate.opsForZSet().remove(queueKey, userId); + + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + redisTemplate.opsForHash().delete(partyKey, userId); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index f8170199..53d47098 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,6 +13,7 @@ import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; +import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; @@ -21,6 +23,7 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -35,7 +38,8 @@ public class ReservationService { private final ReservationRepository reservationRepository; private final UserRepository userRepository; private final WaitingRedisRepository waitingRedisRepository; - + private final StoreRepository storeRepository; + //TODO 성능 비교를 위해 남겨둔 로직 @Transactional(readOnly = true) public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); @@ -66,6 +70,7 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem .reservationList(reservationDtoList) .build(); } + //TODO 성능 비교를 위해 남겨둔 로직 @Transactional public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, MemberDetails memberDetails) { @@ -77,6 +82,57 @@ public CallGetResponseDto updateReservationStatus(Long reservationId, Reservatio reservation.updateStatus(requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } + // Redis queue에 있는 주점별 전체 대기열 조회 + @Transactional(readOnly = true) + public List getAllWaitingUserDetails(Long storeId) { + List> waitingList = waitingRedisRepository.getAllWaitingWithScore(storeId); + + return waitingList.stream() + .map(tuple -> { + String userId = tuple.getValue(); + + // 1. Redis에서 partySize/status 조회 + Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + String status = waitingRedisRepository.getWaitingStatus(storeId, userId); + + // 2. DB에서 userName, createdAt 조회 + String userName = null; + LocalDateTime createdAt = null; + + // UserName 조회 (예: UserRepository) + userName = userRepository.findById(Long.valueOf(userId)) + .map(User::getNickname) + .orElse(null); + + // createAt 조회 (예: ReservationRepository) + createdAt = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) + .map(Reservation::getRequestedAt) + .orElse(null); + + return new WaitingUserResponse( + userId, + partySize, + userName, + createdAt, + status, + tuple.getScore() + ); + }) + .toList(); + } + + // 완료 or 취소 처리된 대기 리스트 조회 + @Transactional(readOnly = true) + public List getCompletedWaitingUserDetails(Long storeId) { + List reservations = reservationRepository.findAllByStore_StoreId(storeId); + + return reservations.stream() + .map(r -> WaitingUserResponse.fromEntity(r)) + .toList(); + } + + + @Transactional public CallingWaitingResponseDto callWaiting(Long storeId, String userId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { @@ -86,16 +142,47 @@ public CallingWaitingResponseDto callWaiting(Long storeId, String userId, Member if (!"WAITING".equals(status)) { throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); } + Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); waitingRedisRepository.setWaitingStatus(storeId, userId, "CALLING"); - LocalDateTime calledAt = LocalDateTime.now(); + // [2] DB에 상태 영구 저장 (없으면 생성, 있으면 상태만 변경) + Reservation reservation = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) + .orElse( + Reservation.builder() + .store(storeRepository.getReferenceById(storeId)) + .user(user) + .requestedAt(LocalDateTime.now()) + .partySize(partySize) + .build() + ); + reservation.updateStatus(ReservationStatus.CALLING); // setter 대신 빌더로 새 객체 or withStatus 패턴 추천 + reservationRepository.save(reservation); return CallingWaitingResponseDto.builder() .storeId(storeId) .userId(userId) - .status("CALLING") - .calledAt(calledAt) + .status(reservation.getStatus().name()) + .calledAt(reservation.getRequestedAt()) .build(); } - //TODO CALLING -> 입장완료 처리 로직 구현 필요(redis에서 삭제 후 RDB에 저장하는게 나을지??) + @Transactional + public String processEntryStatus(Long storeId, String userId, MemberDetails memberDetails, ReservationStatus status) { + // (권한 체크 필요시 여기에 추가) + User user = userRepository.findById(memberDetails.getId()) + .orElseThrow(UserNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + throw new ReservationViewUnauthorizedException(); + } + // 1. DB status 업데이트 + Reservation reservation = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) + .orElseThrow(() -> new IllegalArgumentException("해당 예약이 존재하지 않습니다.")); + reservation.updateStatus(status); + // 2. Redis에서 삭제 + waitingRedisRepository.deleteWaiting(storeId, userId); + + // 메시지 동적 반환 + String action = (status == ReservationStatus.CONFIRMED) ? "입장 완료" : "입장 취소"; + return user.getNickname() + "님의 예약이 " + action + " 처리되었습니다."; + } + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index ad216a56..8741e075 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -13,7 +13,7 @@ import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; -import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto; +import com.nowait.applicationuser.order.dto.OrderResponseDto; import com.nowait.applicationuser.order.service.OrderService; import com.nowait.common.api.ApiUtils; @@ -58,7 +58,7 @@ public ResponseEntity getOrderItems( HttpSession session ) { String sessionId = session.getId(); - List orderItems = orderService.getOrderItemsGroupByStatus(storeId, tableId, sessionId); + List orderItems = orderService.getOrderItemsGroupByOrderId(storeId, tableId, sessionId); return ResponseEntity. status(HttpStatus.OK) .body( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java index dcb675ab..f0f81e7f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderCreateResponseDto.java @@ -1,5 +1,6 @@ package com.nowait.applicationuser.order.dto; +import java.time.LocalDateTime; import java.util.List; import com.nowait.domaincorerdb.order.entity.OrderItem; @@ -22,6 +23,7 @@ public class OrderCreateResponseDto { private List orderItems; // 주문 항목 목록 private OrderStatus status; private Integer totalPrice; + private LocalDateTime createdAt; public static OrderCreateResponseDto fromEntity(UserOrder order,List items) { return OrderCreateResponseDto.builder() @@ -33,6 +35,7 @@ public static OrderCreateResponseDto fromEntity(UserOrder order,List .orderItems(items.stream().map(OrderItemResponseDTO::fromEntity).toList()) .status(order.getStatus()) .totalPrice(order.getTotalPrice()) + .createdAt(order.getCreatedAt()) .build(); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderMenuDto.java similarity index 62% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderMenuDto.java index 1bec7fd3..27ca1d62 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderMenuDto.java @@ -1,8 +1,6 @@ package com.nowait.applicationuser.order.dto; - import com.nowait.domaincorerdb.order.entity.OrderItem; -import com.nowait.domaincorerdb.order.entity.OrderStatus; import lombok.AllArgsConstructor; import lombok.Builder; @@ -13,19 +11,18 @@ @Builder @NoArgsConstructor @AllArgsConstructor -public class OrderItemListGetResponseDto { - private Long orderId; +public class OrderMenuDto { + private Long menuId; private String menuName; private Integer quantity; private Integer price; - public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem) { - return OrderItemListGetResponseDto.builder() - .orderId(orderItem.getUserOrder().getId()) + public static OrderMenuDto fromEntity(OrderItem orderItem) { + return OrderMenuDto.builder() + .menuId(orderItem.getMenu().getId()) .menuName(orderItem.getMenu().getName()) .quantity(orderItem.getQuantity()) .price(orderItem.getMenu().getPrice()) .build(); - } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java similarity index 67% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java index 84d423e9..2f94cf31 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemGroupByStatusResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java @@ -1,5 +1,6 @@ package com.nowait.applicationuser.order.dto; +import java.time.LocalDateTime; import java.util.List; import com.nowait.domaincorerdb.order.entity.OrderStatus; @@ -13,8 +14,9 @@ @Builder @NoArgsConstructor @AllArgsConstructor -public class OrderItemGroupByStatusResponseDto { +public class OrderResponseDto { + private Long orderId; private OrderStatus status; - private List items; + private LocalDateTime createdAt; + private List items; } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 746cf9a8..e8aa4493 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -14,8 +14,8 @@ import com.nowait.applicationuser.order.dto.CartItemDto; import com.nowait.applicationuser.order.dto.OrderCreateRequestDto; import com.nowait.applicationuser.order.dto.OrderCreateResponseDto; -import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto; -import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto; +import com.nowait.applicationuser.order.dto.OrderMenuDto; +import com.nowait.applicationuser.order.dto.OrderResponseDto; import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.order.entity.OrderItem; @@ -94,24 +94,22 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId, } @Transactional(readOnly = true) - public List getOrderItemsGroupByStatus( + public List getOrderItemsGroupByOrderId( Long storeId, Long tableId, String sessionId) { + List userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId); - Map> grouped = userOrders.stream() - .flatMap(order -> order.getOrderItems().stream()) - .collect(Collectors.groupingBy( - orderItem -> orderItem.getUserOrder().getStatus(), - Collectors.mapping( - OrderItemListGetResponseDto::fromEntity, - Collectors.toList() + // orderId 기준으로 바로 변환 + return userOrders.stream() + .map(order -> OrderResponseDto.builder() + .orderId(order.getId()) + .status(order.getStatus()) + .createdAt(order.getCreatedAt()) + .items( + order.getOrderItems().stream() + .map(OrderMenuDto::fromEntity) + .toList() ) - )); - - return grouped.entrySet().stream() - .map(entry -> OrderItemGroupByStatusResponseDto.builder() - .status(entry.getKey()) - .items(entry.getValue()) .build()) .toList(); } @@ -121,6 +119,7 @@ public List getOrderItemsGroupByStatus( + private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { if (storeId == null || tableId == null || orderCreateRequestDto == null) { throw new OrderParameterEmptyException(); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index 219f002d..32742420 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -24,7 +24,7 @@ import lombok.NoArgsConstructor; @Entity -@Table(name = "waiting_requests") +@Table(name = "reservation") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 235242b9..ec39267c 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -1,6 +1,7 @@ package com.nowait.domaincorerdb.reservation.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -15,4 +16,8 @@ public interface ReservationRepository extends JpaRepository List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses); + Optional findByStore_StoreIdAndUserId(Long storeId, Long userId); + + List findAllByStore_StoreId(Long storeId); + } From f1399095768a3edde27d4347f3b26136b71a7232 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 22 Jul 2025 18:58:26 +0900 Subject: [PATCH 267/685] =?UTF-8?q?feat(Order,Reservation):=20=EC=A3=BC?= =?UTF-8?q?=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=EC=97=B4=20CRUD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order 조회 응답 데이터 구조 변경 - 관리자 측 대기열 조회,변경 로직 구현 --- .../reservation/dto/CallGetResponseDto.java | 13 +++++++++++++ .../reservation/dto/ReservationGetResponseDto.java | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java index a8100975..cecc91b7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java @@ -4,17 +4,30 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter @Builder +@Schema(description = "호출(대기) 사용자 응답 DTO") public class CallGetResponseDto { + @Schema(description = "예약 ID", example = "123") private Long id; + + @Schema(description = "가게 ID", example = "99") private Long storeId; + + @Schema(description = "사용자 닉네임", example = "혜민이") private String userName; + + @Schema(description = "예약 요청 시각", example = "2025-07-22T14:45:09.492253") private LocalDateTime requestedAt; + + @Schema(description = "예약 상태", example = "CALLING") private String status; + + @Schema(description = "파티 인원 수", example = "4") private Integer partySize; public static CallGetResponseDto fromEntity(Reservation reservation) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java index 6bb7e5af..14933902 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java @@ -4,17 +4,31 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter @Builder +@Schema(description = "예약 상세 조회 응답 DTO") public class ReservationGetResponseDto { + + @Schema(description = "예약 ID", example = "123") private Long id; + + @Schema(description = "매장 ID", example = "99") private Long storeId; + + @Schema(description = "사용자 이름(닉네임)", example = "혜민이") private String userName; + + @Schema(description = "예약 요청 시각", example = "2025-07-22T14:45:09.492253") private LocalDateTime requestedAt; + + @Schema(description = "예약 상태", example = "WAITING") private String status; + + @Schema(description = "파티 인원", example = "2") private Integer partySize; public static ReservationGetResponseDto fromEntity(Reservation reservation) { From 32c67ffe1a943543e381510a7581245435bb4af8 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 22 Jul 2025 19:10:53 +0900 Subject: [PATCH 268/685] =?UTF-8?q?feat(Order):=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order 조회 응답 데이터 totalPrice 추가 --- .../reservation/dto/WaitingUserResponse.java | 19 +++++++++++++++++-- .../service/ReservationService.java | 3 ++- .../order/dto/OrderResponseDto.java | 1 + .../order/service/OrderService.java | 1 + 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index 982e821c..eb8d3d37 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -4,20 +4,35 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; + @Getter @AllArgsConstructor @Builder +@Schema(description = "대기 사용자 응답 DTO") public class WaitingUserResponse { - private String id; // userId + + @Schema(description = "유저 ID", example = "1201") + private String id; // userId + + @Schema(description = "파티 인원", example = "3") private Integer partySize; + + @Schema(description = "사용자 이름(닉네임)", example = "혜민이") private String userName; + + @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") private LocalDateTime createdAt; + + @Schema(description = "대기 상태", example = "CALLING") private String status; - private Double score; // (필요시, 대기열 정렬 등) + + @Schema(description = "대기 순번/점수", example = "2.0") + private Double score; public static WaitingUserResponse fromEntity(Reservation reservation) { return WaitingUserResponse.builder() diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 53d47098..caa97d81 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -131,7 +131,7 @@ public List getCompletedWaitingUserDetails(Long storeId) { .toList(); } - + // 대기 객체 호출 (WAITING -> CALLING) @Transactional public CallingWaitingResponseDto callWaiting(Long storeId, String userId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); @@ -163,6 +163,7 @@ public CallingWaitingResponseDto callWaiting(Long storeId, String userId, Member .calledAt(reservation.getRequestedAt()) .build(); } + // 대기 객체 상태 변경 @Transactional public String processEntryStatus(Long storeId, String userId, MemberDetails memberDetails, ReservationStatus status) { // (권한 체크 필요시 여기에 추가) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java index 2f94cf31..117c598e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderResponseDto.java @@ -19,4 +19,5 @@ public class OrderResponseDto { private OrderStatus status; private LocalDateTime createdAt; private List items; + private Integer totalPrice; } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index e8aa4493..254caac4 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -110,6 +110,7 @@ public List getOrderItemsGroupByOrderId( .map(OrderMenuDto::fromEntity) .toList() ) + .totalPrice(order.getTotalPrice()) .build()) .toList(); } From 4a200ec93e1031c2c161c1c405dd639b0906b793 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 19:25:28 +0900 Subject: [PATCH 269/685] =?UTF-8?q?refactor(Statistics):=20LocalDate=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 5 +++-- .../controller/StatisticsController.java | 17 ++++++++++++++--- .../statistic/dto/OrderSalesSumDetail.java | 10 +++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index b2f8c701..105afbfe 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.order.service; +import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; @@ -81,7 +82,7 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus } @Transactional(readOnly = true) - public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails) { + public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails, LocalDate date) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); @@ -89,7 +90,7 @@ public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails) { throw new OrderViewUnauthorizedException(); } - return statisticCustomRepository.findSalesSumByStoreId(storeId); + return statisticCustomRepository.findSalesSumByStoreId(storeId, date); } @Transactional(readOnly = true) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java index 2fea19c0..e552f624 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java @@ -1,12 +1,15 @@ package com.nowait.applicationadmin.statistic.controller; +import java.time.LocalDate; import java.util.List; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationadmin.order.service.OrderService; @@ -34,10 +37,18 @@ public class StatisticsController { private final PopularMenuRedisService popularMenuRedisService; @GetMapping("/sales") - @Operation(summary = "오늘의 매출 조회", description = "오늘의 매출을 조회합니다.") + @Operation(summary = "지정일 매출 조회", description = "날짜(date) 파라미터로 매출을 조회합니다. 포맷: yyyy-MM-dd") @ApiResponse(responseCode = "200", description = "오늘의 매출 조회 성공") - public ResponseEntity getTodaySales(@AuthenticationPrincipal MemberDetails memberDetails) { - OrderSalesSumDetail sales = orderService.getSaleSumByStoreId(memberDetails); + public ResponseEntity getTodaySales( + @AuthenticationPrincipal MemberDetails memberDetails, + @RequestParam(value = "date", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) { + LocalDate targetDate = (date != null ? date : LocalDate.now()); + OrderSalesSumDetail sales = orderService.getSaleSumByStoreId(memberDetails, targetDate); + + if (sales.isAllZero()) { + return ResponseEntity.ok(ApiUtils.success("해당일 매출 데이터가 없습니다.")); + } return ResponseEntity .status(HttpStatus.OK) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java index e3ac3ef8..e516f403 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java @@ -1,5 +1,7 @@ package com.nowait.domainadminrdb.statistic.dto; +import java.time.LocalDate; + import lombok.Builder; import lombok.Getter; @@ -10,12 +12,18 @@ public class OrderSalesSumDetail { private Integer todaySalesSum; private Integer yesterdaySalesSum; private Integer cumulativeSalesBeforeYesterday; + private LocalDate date; public OrderSalesSumDetail(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, - Integer cumulativeSalesBeforeYesterday) { + Integer cumulativeSalesBeforeYesterday, LocalDate date) { this.storeId = storeId; this.todaySalesSum = todaySalesSum; this.yesterdaySalesSum = yesterdaySalesSum; this.cumulativeSalesBeforeYesterday = cumulativeSalesBeforeYesterday; + this.date = date; + } + + public boolean isAllZero() { + return todaySalesSum == 0 && yesterdaySalesSum == 0 && cumulativeSalesBeforeYesterday == 0; } } From 4b164a20f2dd53d1e18c7a04fc63b74aa2b5c2b0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 22 Jul 2025 19:25:51 +0900 Subject: [PATCH 270/685] =?UTF-8?q?refactor(Statistics):=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=90=EB=B3=84=20=EB=A7=A4=EC=B6=9C=20=EB=9E=AD=ED=82=B9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=A3=BC=EB=AC=B8=20=EA=B1=B4=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/dto/StoreRankingDto.java | 4 +- .../service/impl/RankingServiceImpl.java | 10 +++- .../repository/StatisticCustomRepository.java | 6 +- .../StatisticCustomRepositoryImpl.java | 60 ++++++++++++------- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java index a32d6162..36a014c1 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java @@ -9,17 +9,19 @@ public class StoreRankingDto { private final Long departmentId; private final String departmentName; private final Integer totalSales; + private final Integer orderCount; private final Long currentRank; private final Integer delta; private final String profileUrl; public StoreRankingDto(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales, - Long currentRank, Integer delta, String profileUrl) { + Integer orderCount, Long currentRank, Integer delta, String profileUrl) { this.storeId = storeId; this.storeName = storeName; this.departmentId = departmentId; this.departmentName = departmentName; this.totalSales = totalSales; + this.orderCount = orderCount; this.currentRank = currentRank; this.delta = delta; this.profileUrl = profileUrl; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java index af206acc..82528132 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java @@ -31,7 +31,6 @@ public class RankingServiceImpl implements RankingService { private final UserRepository userRepository; private final RankingQueryService rankingQuery; - @Override @Transactional(readOnly = true) public List getStatisticsRankings(MemberDetails memberDetails) { @@ -45,27 +44,32 @@ public List getStatisticsRankings(MemberDetails memberDetails) // 1) Redis에서 Top4+내주점: storeId, totalSales, currentRank, delta List entries = rankingQuery.getRankings(userStoreId, 5); - // 2) redis 에서 storeId 정보 가져오기 + // 2) 주문 건수 조회 List storeIds = entries.stream() .map(RankingEntry::getStoreId) .toList(); + Map orderCountMap = statisticCustomRepository.findOrderCountByStoreIds(storeIds); + + // 3) redis 에서 storeId 정보 가져오기 List infos = statisticCustomRepository.findStoreInfoByIds(storeIds); // StoreInfo를 storeId로 매핑 Map infoMap = infos.stream() .collect(Collectors.toMap(StoreInfo::getStoreId, Function.identity())); - // 3) 매핑 → 최종 DTO + // 4) 매핑 → 최종 DTO return entries.stream() .map(e -> { StoreInfo info = infoMap.get(e.getStoreId()); + Integer orderCount = orderCountMap.getOrDefault(e.getStoreId(), 0); return new StoreRankingDto( e.getStoreId(), info.getStoreName(), info.getDepartmentId(), info.getDepartmentName(), e.getTotalSales(), + orderCount, e.getCurrentRank(), e.getDelta(), info.getProfileUrl() diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java index 8c03bcdd..fcdeeaf7 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java @@ -1,6 +1,8 @@ package com.nowait.domainadminrdb.statistic.repository; +import java.time.LocalDate; import java.util.List; +import java.util.Map; import com.nowait.domainadminrdb.statistic.dto.OrderSalesSumDetail; import com.nowait.domainadminrdb.statistic.dto.StoreInfo; @@ -11,10 +13,12 @@ public interface StatisticCustomRepository { - OrderSalesSumDetail findSalesSumByStoreId(Long storeId); + OrderSalesSumDetail findSalesSumByStoreId(Long storeId, LocalDate date); List getTop4PlusMine(Long storeId); + Map findOrderCountByStoreIds(List storeIds); + // redis 사용하는 부분 List findTotalSales(); diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java index 0985b9b9..bcee06b8 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java @@ -45,25 +45,19 @@ public StatisticCustomRepositoryImpl(JPAQueryFactory queryFactory) { private static final QStoreImage si = QStoreImage.storeImage; @Override - public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { - // 1. 날짜 기준 설정 (시작은 자정, 끝은 다음 날 자정) - LocalDate today = LocalDate.now(); - LocalDate yesterday = today.minusDays(1); - - LocalDateTime todayStart = today.atStartOfDay(); - LocalDateTime todayEnd = today.plusDays(1).atStartOfDay(); // 내일 00:00:00 - - LocalDateTime yesterdayStart = yesterday.atStartOfDay(); - LocalDateTime yesterdayEnd = today.atStartOfDay(); + public OrderSalesSumDetail findSalesSumByStoreId(Long storeId, LocalDate date) { + // 1) 시작, 끝 날짜 설정 + LocalDateTime start = date.atStartOfDay(); // 해당 날짜의 자정 + LocalDateTime end = date.plusDays(1).atStartOfDay(); - // 2. 오늘 매출 합산 - Integer todaySum = queryFactory + // 2) target 날짜 해당하는 매출 합산 + Integer targetSum = queryFactory .select(u.totalPrice.sum()) .from(u) .where( u.store.storeId.eq(storeId), - u.createdAt.goe(todayStart), - u.createdAt.lt(todayEnd), + u.createdAt.goe(start), + u.createdAt.lt(end), u.status.eq(COOKED) ) .fetchOne(); @@ -74,8 +68,8 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { .from(u) .where( u.store.storeId.eq(storeId), - u.createdAt.goe(yesterdayStart), - u.createdAt.lt(yesterdayEnd), + u.createdAt.goe(start), + u.createdAt.lt(start.minusDays(1)), u.status.eq(COOKED) ) .fetchOne(); @@ -85,21 +79,21 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { .from(u) .where( u.store.storeId.eq(storeId), - u.createdAt.lt(yesterdayEnd), + u.createdAt.lt(start), u.status.eq(COOKED) ) .fetchOne(); // null 방어 처리 - if (todaySum == null) - todaySum = 0; + if (targetSum == null) + targetSum = 0; if (yesterdaySum == null) yesterdaySum = 0; if (cumulativeSalesBeforeYesterday == null) cumulativeSalesBeforeYesterday = 0; // 4. 응답 객체 생성 - return new OrderSalesSumDetail(storeId, todaySum, yesterdaySum, cumulativeSalesBeforeYesterday); + return new OrderSalesSumDetail(storeId, targetSum, yesterdaySum, cumulativeSalesBeforeYesterday, date); } @Override @@ -148,6 +142,32 @@ public List getTop4PlusMine(Long userStoreId) { return result; } + @Override + public Map findOrderCountByStoreIds(List storeIds) { + LocalDate today = LocalDate.now(); + LocalDateTime start = today.atStartOfDay(); + LocalDateTime end = today.plusDays(1).atStartOfDay(); + + List rows = queryFactory + .select(u.store.storeId, u.count()) + .from(u) + .where( + u.store.storeId.in(storeIds), + u.createdAt.goe(start), + u.createdAt.lt(end), + u.status.eq(COOKED) + ) + .groupBy(u.store.storeId) + .fetch(); + + // Tuple → Map + return rows.stream() + .collect(Collectors.toMap( + t -> t.get(u.store.storeId), + t -> t.get(u.count()).intValue() + )); + } + private List getAllStores(LocalDateTime todayStart, LocalDateTime todayEnd) { return queryFactory .select(u.store.storeId, u.store.name, u.store.departmentId, u.totalPrice.sum()) From a39dfa4b7d0c042b5473a55d6ec0aeabd3a2f0b8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 23 Jul 2025 18:35:19 +0900 Subject: [PATCH 271/685] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-domain/domain-core-rdb/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/build.gradle b/nowait-domain/domain-core-rdb/build.gradle index 954fbd42..48bcad51 100644 --- a/nowait-domain/domain-core-rdb/build.gradle +++ b/nowait-domain/domain-core-rdb/build.gradle @@ -39,7 +39,6 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.26' //QueryDsl - // TODO Q클래스 생성시 오류 발생 해결 필요 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" From a2d490e1184b14dbc32041c9e5ca6e23ae9aa569 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 23 Jul 2025 18:35:39 +0900 Subject: [PATCH 272/685] =?UTF-8?q?refactor(User):=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EC=A0=9C=EC=95=BD=EC=A1=B0=EA=B1=B4=EC=97=90=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/user/dto/ManagerSignupRequestDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java index 8b327e01..7f807bea 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java @@ -28,7 +28,7 @@ public class ManagerSignupRequestDto { private String password; @NotBlank - @Pattern(regexp = "^[a-zA-Z가-힣]{2,12}$") + @Pattern(regexp = "^[a-zA-Z가-힣0-9]{2,12}$", message = "2~12자 사이의 영문, 한글, 숫자만 입력 가능합니다.") @Schema(description = "이름(예시)", example = "김노웻") private String nickname; From a5896d5a6c00efd7535bbad66a9bd19b9ee2c20d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 23 Jul 2025 18:36:08 +0900 Subject: [PATCH 273/685] =?UTF-8?q?refactor:=20QueryDslConfig=20domain-cor?= =?UTF-8?q?e=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/domaincorerdb}/config/QueryDslConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename nowait-domain/{domain-admin-rdb/src/main/java/com/nowait/domainadminrdb => domain-core-rdb/src/main/java/com/nowait/domaincorerdb}/config/QueryDslConfig.java (91%) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java similarity index 91% rename from nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java rename to nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java index 6c04df39..f75d497b 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/config/QueryDslConfig.java @@ -1,4 +1,4 @@ -package com.nowait.domainadminrdb.config; +package com.nowait.domaincorerdb.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; From c5d77572d10eca569ff6b4b58df03d6a9cd54cd5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 23 Jul 2025 18:36:33 +0900 Subject: [PATCH 274/685] =?UTF-8?q?refactor(Store):=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20keyword=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/controller/StoreController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index b39a53a8..1373b875 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -88,12 +88,12 @@ public ResponseEntity getStoreById(@PathVariable Long storeId) { @GetMapping("/search") @Operation(summary = "주점 이름으로 주점 검색", description = "주점 이름을 기준으로 주점을 검색합니다.") @ApiResponse(responseCode = "200", description = "주점 검색 성공") - public ResponseEntity searchStores(@RequestParam("name") String name) { + public ResponseEntity searchStores(@RequestParam("keyword") String keyword) { return ResponseEntity .ok() .body( ApiUtils.success( - storeService.searchStoresByName(name) + storeService.searchByKeywordNative(keyword) ) ); } From cf3574af2619c4ad7c9d0a5c2d3385c06eb9d8b1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 23 Jul 2025 18:37:04 +0900 Subject: [PATCH 275/685] =?UTF-8?q?refactor(Store):=20QueryDSL=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=20repository=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/repository/StoreCustomRepository.java | 4 ++++ .../repository/StoreCustomRepositoryImpl.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepository.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepositoryImpl.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepository.java new file mode 100644 index 00000000..4d6a1f2a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepository.java @@ -0,0 +1,4 @@ +package com.nowait.domaincorerdb.store.repository; + +public interface StoreCustomRepository { +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepositoryImpl.java new file mode 100644 index 00000000..82820c1d --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreCustomRepositoryImpl.java @@ -0,0 +1,17 @@ +package com.nowait.domaincorerdb.store.repository; + +import org.springframework.stereotype.Repository; + +import com.nowait.domaincorerdb.store.entity.QStore; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class StoreCustomRepositoryImpl implements StoreCustomRepository { + + private final JPAQueryFactory queryFactory; + + private final QStore store = QStore.store; +} From a0540aa604769054eab65bb92e418a5f9dd3fb4f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 23 Jul 2025 18:37:36 +0900 Subject: [PATCH 276/685] =?UTF-8?q?refactor(Store):=20findByNameContaining?= =?UTF-8?q?IgnoreCaseAndDeletedFalse=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20?= =?UTF-8?q?=ED=92=80=ED=85=8D=EC=8A=A4=ED=8A=B8=EC=9D=B8=EB=8D=B1=EC=8A=A4?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20searchByK?= =?UTF-8?q?eywordNative=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreService.java | 2 +- .../store/service/StoreServiceImpl.java | 7 ++++--- .../store/repository/StoreRepository.java | 20 +++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 3df99912..78fe8738 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -19,6 +19,6 @@ public interface StoreService { StorePageReadDto getStoreByStoreId(Long storeId); - List searchStoresByName(String name); + List searchByKeywordNative(String name); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 8b9a5a5e..e365df8f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -144,13 +144,14 @@ public StorePageReadDto getStoreByStoreId(Long storeId) { } @Override - public List searchStoresByName(String name) { - if (name == null || name.isBlank()) { + public List searchByKeywordNative(String keyword) { + if (keyword == null || keyword.isBlank()) { throw new StoreParamEmptyException(); } // 1) 페이징된 Store 스냅샷 조회 - List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(name); + // List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(keyword); + List stores = storeRepository.searchByKeywordNative(keyword); // 2) 각 StoreId / Department ID 추출 List storeIds = stores.stream() diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index fbc0ebca..283a1a0e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -8,22 +8,30 @@ import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.nowait.domaincorerdb.store.entity.Store; @Repository -public interface StoreRepository extends JpaRepository { +public interface StoreRepository extends JpaRepository, StoreCustomRepository { List findAllByDeletedFalse(); Optional findByStoreIdAndDeletedFalse(Long storeId); - List findByNameContainingIgnoreCaseAndDeletedFalse(String name); - Slice findAllByDeletedFalseOrderByStoreIdAsc(Pageable pageable); - // TODO queryDSL으로 전환? - @Query("select s.storeId from Store s where s.isActive = true and s.deleted = false") - List findAllActiveStoreIds(); + @Query(value = """ + SELECT DISTINCT s.* + FROM stores s + LEFT JOIN departments d ON s.department_id = d.id + WHERE s.deleted = false + AND ( + MATCH(s.name) AGAINST(:kw) + OR MATCH(d.name) AGAINST(:kw) + ) + """, + nativeQuery = true) + List searchByKeywordNative(@Param("kw") String booleanKeyword); } From 5dbb4bcebb6d6af316a1e8bf32e9447240d41d36 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 24 Jul 2025 10:51:10 +0900 Subject: [PATCH 277/685] =?UTF-8?q?feat(Store):=20=EC=A3=BC=EC=A0=90=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 4 +-- .../store/controller/StoreController.java | 17 +++++++++++ .../store/dto/StoreWaitingInfo.java | 19 +++++++++++++ .../store/service/StoreService.java | 3 ++ .../store/service/StoreServiceImpl.java | 28 +++++++++++++++++++ 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index a6871ed0..97920fb8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -33,7 +33,7 @@ public class ReservationController { private final ReservationService reservationService; @GetMapping("/admin/{storeId}/waiting/users") - @Operation(summary = "주점별 전체 대기 리스트 조회", description = "주점에 대한 대기 리스트 조회(WAITING,CALLING)") + @Operation(summary = "주점별 전체 대기/호출중 리스트 조회", description = "주점에 대한 대기 리스트 조회(WAITING,CALLING)") @ApiResponse(responseCode = "200", description = "주점별 전체 대기 리스트 조회") public ResponseEntity> getWaitingUsersWithScore(@PathVariable Long storeId) { List response = reservationService.getAllWaitingUserDetails(storeId); @@ -41,7 +41,7 @@ public ResponseEntity> getWaitingUsersWithScore(@PathV } @GetMapping("/admin/{storeId}/completed") - @Operation(summary = "주점별 전체 완료 리스트 조회", description = "주점에 대한 완료/취소 리스트 조회(CANCELED,CONFIRMED)") + @Operation(summary = "주점별 전체 취소/완료 리스트 조회", description = "주점에 대한 완료/취소 리스트 조회(CANCELED,CONFIRMED)") @ApiResponse(responseCode = "200", description = "주점별 전체 완료/취소 리스트 조회") public ResponseEntity getCompletedReservationList( @PathVariable Long storeId, diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index b39a53a8..b495ca0b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.applicationuser.store.service.StoreService; import com.nowait.common.api.ApiUtils; @@ -97,4 +98,20 @@ public ResponseEntity searchStores(@RequestParam("name") String name) { ) ); } + + @GetMapping("/waiting-list") + @Operation(summary = "예약 많은순/적은순 주점 리스트 조회", description = "desc(대기 많은순) , asc(대기 적은순)") + @ApiResponse(responseCode = "200", description = "주점 대기순 정렬") + public ResponseEntity getStoreWaitingList( + @RequestParam(defaultValue = "desc") String order) { + boolean desc = !"asc".equalsIgnoreCase(order); // 기본: 대기 많은 순 + + return ResponseEntity + .ok() + .body( + ApiUtils.success( + storeService.getStoresByWaitingCount(desc) + ) + ); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java new file mode 100644 index 00000000..56249932 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java @@ -0,0 +1,19 @@ +package com.nowait.applicationuser.store.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "주점 대기 현황 정보 DTO") +public class StoreWaitingInfo { + @Schema(description = "주점 ID", example = "1") + private String storeId; + + @Schema(description = "주점명", example = "노웨이팅 치킨") + private String storeName; + + @Schema(description = "대기 인원 수", example = "12") + private Long waitingCount; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 3df99912..1e85f7c0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -8,6 +8,7 @@ import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StoreReadDto; import com.nowait.applicationuser.store.dto.StoreReadResponse; +import com.nowait.applicationuser.store.dto.StoreWaitingInfo; public interface StoreService { @@ -21,4 +22,6 @@ public interface StoreService { List searchStoresByName(String name); + List getStoresByWaitingCount(boolean desc); + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 8b9a5a5e..9048a18d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -2,10 +2,12 @@ import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,6 +16,7 @@ import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StoreReadDto; import com.nowait.applicationuser.store.dto.StoreReadResponse; +import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.store.entity.Store; @@ -32,6 +35,7 @@ public class StoreServiceImpl implements StoreService { private final StoreRepository storeRepository; private final StoreImageRepository storeImageRepository; private final DepartmentRepository departmentRepository; + private final StringRedisTemplate redisTemplate; @Override @@ -188,4 +192,28 @@ public List searchStoresByName(String name) { }) .toList(); } + + // 주점 대기 리스트 반환 (많은 순/적은 순) + @Transactional(readOnly = true) + public List getStoresByWaitingCount(boolean desc) { + // 1. 모든 waiting:{storeId} key 조회 (패턴 탐색) + Set keys = redisTemplate.keys("waiting:*"); + if (keys == null) return List.of(); + + List result = keys.stream() + .filter(key -> key.startsWith("waiting:") && !key.startsWith("waiting:party:")) + .filter(key -> "zset".equals(redisTemplate.type(key).code())) // 타입 확인 확실히! + .map(key -> { + Long count = redisTemplate.opsForZSet().zCard(key); + String storeId = key.replace("waiting:", ""); + String storeName = storeRepository.findById(Long.valueOf(storeId)) + .map(Store::getName) + .orElse("UNKNOWN"); + return new StoreWaitingInfo(storeId, storeName, count != null ? count : 0); + }) + .sorted((a, b) -> b.getWaitingCount().compareTo(a.getWaitingCount())) + .toList(); + + return result; + } } From 5b61099fa7834fbb2377f7cb8c62e610184dd7c6 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 24 Jul 2025 10:58:58 +0900 Subject: [PATCH 278/685] =?UTF-8?q?feat(Store):=20=EC=A3=BC=EC=A0=90=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/service/StoreServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 9048a18d..0eb1538b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -202,7 +202,7 @@ public List getStoresByWaitingCount(boolean desc) { List result = keys.stream() .filter(key -> key.startsWith("waiting:") && !key.startsWith("waiting:party:")) - .filter(key -> "zset".equals(redisTemplate.type(key).code())) // 타입 확인 확실히! + .filter(key -> "zset".equals(redisTemplate.type(key).code())) .map(key -> { Long count = redisTemplate.opsForZSet().zCard(key); String storeId = key.replace("waiting:", ""); @@ -211,7 +211,9 @@ public List getStoresByWaitingCount(boolean desc) { .orElse("UNKNOWN"); return new StoreWaitingInfo(storeId, storeName, count != null ? count : 0); }) - .sorted((a, b) -> b.getWaitingCount().compareTo(a.getWaitingCount())) + .sorted((a, b) -> desc ? + b.getWaitingCount().compareTo(a.getWaitingCount()) : + a.getWaitingCount().compareTo(b.getWaitingCount())) .toList(); return result; From 7eb9608e4f6c33584e009a8388dda8ea6073dc7b Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Thu, 24 Jul 2025 22:58:19 +0900 Subject: [PATCH 279/685] =?UTF-8?q?feat(Reservation):=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=20=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index eb8d3d37..71c1c490 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -16,8 +16,11 @@ @Schema(description = "대기 사용자 응답 DTO") public class WaitingUserResponse { - @Schema(description = "유저 ID", example = "1201") - private String id; // userId + @Schema(description = "예약 ID", example = "1201") + private String id; // reservationId + + @Schema(description = "유저 ID", example = "16") + private String userId; @Schema(description = "파티 인원", example = "3") private Integer partySize; @@ -37,6 +40,7 @@ public class WaitingUserResponse { public static WaitingUserResponse fromEntity(Reservation reservation) { return WaitingUserResponse.builder() .id(reservation.getId().toString()) + .userId(reservation.getUser().getId().toString()) .partySize(reservation.getPartySize()) .userName(reservation.getUser().getNickname()) .createdAt(reservation.getRequestedAt()) From cb090401eb3e48d291212cd18ad2fd29ca2b5523 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 25 Jul 2025 11:51:34 +0900 Subject: [PATCH 280/685] =?UTF-8?q?refactor(Store):=20store=20waiting=20co?= =?UTF-8?q?unt=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/store/dto/StorePageReadDto.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java index 050cc091..29cd7194 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java @@ -15,6 +15,7 @@ @Builder public class StorePageReadDto { private Long storeId; + private Long waitingCount; private Long departmentId; private String departmentName; private String name; @@ -26,7 +27,7 @@ public class StorePageReadDto { private Boolean deleted; private LocalDateTime createdAt; - public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName) { + public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount) { StoreImageUploadResponse profile = allImages.stream() .filter(image -> image.getImageType() == ImageType.PROFILE) @@ -40,6 +41,7 @@ public static StorePageReadDto fromEntity(Store store, List Date: Fri, 25 Jul 2025 11:51:53 +0900 Subject: [PATCH 281/685] =?UTF-8?q?refactor(Store):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20controller=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index d8f6a59f..4a7e6653 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -28,18 +28,6 @@ public class StoreController { private final StoreService storeService; - @GetMapping("/all-stores") - @Operation(summary = "모든 주점 조회", description = "모든 주점을 조회합니다.") - @ApiResponse(responseCode = "200", description = "모든 주점 조회 성공") - public ResponseEntity getAllStores() { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - storeService.getAllStores() - ) - ); - } @GetMapping("/all-stores/infinite-scroll") @Operation( @@ -57,22 +45,6 @@ public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable) { ); } - @GetMapping("/low-wait/infinite-scroll") - @Operation( - summary = "모든 주점 페이지네이션 조회", - description = "모든 주점을 페이지네이션으로 조회합니다." - ) - @ApiResponse(responseCode = "200", description = "모든 주점 페이지네이션 조회 성공") - public ResponseEntity getAllStores(Pageable pageable) { - return ResponseEntity - .ok() - .body( - ApiUtils.success( - storeService.getAllStoresByPage(pageable) - ) - ); - } - @GetMapping("/{storeId}") @Operation(summary = "주점 ID로 주점 상세 조회", description = "특정 주점을 ID로 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 상세 조회 성공") From 5447f7be8bc88bd32cefe8bf7426ee9458723298 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 25 Jul 2025 11:52:14 +0900 Subject: [PATCH 282/685] =?UTF-8?q?refactor(Store):=20store=20waiting=20co?= =?UTF-8?q?unt=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreService.java | 4 - .../store/service/StoreServiceImpl.java | 79 ++++++++----------- 2 files changed, 34 insertions(+), 49 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index f8e01bd7..35588fce 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -12,10 +12,6 @@ public interface StoreService { - StoreReadResponse getAllStores(); - - StoreReadResponse getAllStoresByPage(Pageable pageable); - StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable); StorePageReadDto getStoreByStoreId(Long storeId); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 6fbde78e..23ba3a83 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.data.domain.Pageable; @@ -14,8 +15,6 @@ import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; import com.nowait.applicationuser.store.dto.StoreImageUploadResponse; import com.nowait.applicationuser.store.dto.StorePageReadDto; -import com.nowait.applicationuser.store.dto.StoreReadDto; -import com.nowait.applicationuser.store.dto.StoreReadResponse; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; @@ -38,46 +37,6 @@ public class StoreServiceImpl implements StoreService { private final StringRedisTemplate redisTemplate; - @Override - @Transactional(readOnly = true) - public StoreReadResponse getAllStores() { - List stores = storeRepository.findAllByDeletedFalse(); - - List storeRead = stores.stream() - .map(store -> { - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - return StoreReadDto.fromEntity(store, imageDto); - }) - .toList(); - - boolean hasNext = false; - - return StoreReadResponse.of(storeRead, hasNext); - } - - @Override - @Transactional(readOnly = true) - public StoreReadResponse getAllStoresByPage(Pageable pageable) { - Slice stores = storeRepository.findAllByDeletedFalseOrderByStoreIdAsc(pageable); - - List storeRead = stores.getContent().stream() - .map(store -> { - List images = storeImageRepository.findByStore(store); - List imageDto = images.stream() - .map(StoreImageUploadResponse::fromEntity) - .toList(); - return StoreReadDto.fromEntity(store, imageDto); - }) - .toList(); - - boolean hasNext = stores.hasNext(); - - return StoreReadResponse.of(storeRead, hasNext); - } - @Override @Transactional(readOnly = true) public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable) { @@ -94,6 +53,16 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page .distinct() .toList(); + // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 + Map waitingSizeMap = storeIds.stream() + .collect(Collectors.toMap( + Function.identity(), + storeId -> { + String key = "waiting:" + storeId; + return redisTemplate.opsForZSet().zCard(key); + } + )); + // 3) 각 StoreId에 해당하는 이미지 조회 List allImages = storeImageRepository.findByStore_StoreIdIn(storeIds); Map> imageMap = allImages.stream() @@ -118,7 +87,9 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page .getOrDefault(store.getStoreId(), List.of()); String departmentName = deptNameMap .getOrDefault(store.getDepartmentId(), "Unknown Department"); - return StorePageReadDto.fromEntity(store, imgs, departmentName); + Long waitingCount = + waitingSizeMap.getOrDefault(store.getStoreId(), 0L); + return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount); }) .toList(); @@ -139,12 +110,16 @@ public StorePageReadDto getStoreByStoreId(Long storeId) { .map(Department::getName) .orElse("Unknown Department"); + // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 + String key = "waiting:" + storeId; + Long waitingSize = redisTemplate.opsForZSet().zCard(key); + List images = storeImageRepository.findByStore(store); List imageDto = images.stream() .map(StoreImageUploadResponse::fromEntity) .toList(); - return StorePageReadDto.fromEntity(store, imageDto, departmentName); + return StorePageReadDto.fromEntity(store, imageDto, departmentName, waitingSize); } @Override @@ -154,7 +129,9 @@ public List searchByKeywordNative(String keyword) { } // 1) 페이징된 Store 스냅샷 조회 + // Like 사용 // List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(keyword); + // 풀텍스트인덱스 사용 List stores = storeRepository.searchByKeywordNative(keyword); // 2) 각 StoreId / Department ID 추출 @@ -166,6 +143,16 @@ public List searchByKeywordNative(String keyword) { .distinct() .toList(); + // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 + Map waitingSizeMap = storeIds.stream() + .collect(Collectors.toMap( + Function.identity(), + storeId -> { + String key = "waiting:" + storeId; + return redisTemplate.opsForZSet().zCard(key); + } + )); + // 3) 각 StoreId에 해당하는 이미지 조회 List allImages = storeImageRepository.findByStore_StoreIdIn(storeIds); Map> imageMap = allImages.stream() @@ -189,7 +176,9 @@ public List searchByKeywordNative(String keyword) { .getOrDefault(store.getStoreId(), List.of()); String departmentName = deptNameMap .getOrDefault(store.getDepartmentId(), "Unknown Department"); - return StorePageReadDto.fromEntity(store, imgs, departmentName); + Long waitingCount = + waitingSizeMap.getOrDefault(store.getStoreId(), 0L); + return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount); }) .toList(); } From c35583092ce7ed8248a9cb017cbe84109b895c20 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 12:10:10 +0900 Subject: [PATCH 283/685] =?UTF-8?q?feat(Store,Reservation):=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EB=A7=8E=EC=9D=80/=EC=A0=81=EC=9D=80=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - redis.keys()로 다 불러오는 로직 ->scan*cursor 활용 --- .../controller/ReservationController.java | 2 +- .../store/service/StoreServiceImpl.java | 69 ++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index 97920fb8..d0925567 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -66,7 +66,7 @@ public ResponseEntity callWaiting(@PathVariable Long storeId, ) ); } - @PostMapping("/admin/update/{storeId}/{userId}/{status}") + @PatchMapping("/admin/update/{storeId}/{userId}/{status}") @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") public ResponseEntity updateEntry( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 0eb1538b..fcf16fa9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -1,5 +1,8 @@ package com.nowait.applicationuser.store.service; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; @@ -7,6 +10,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +33,7 @@ import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; import com.nowait.domaincorerdb.store.repository.StoreImageRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import lombok.RequiredArgsConstructor; @@ -196,26 +205,54 @@ public List searchStoresByName(String name) { // 주점 대기 리스트 반환 (많은 순/적은 순) @Transactional(readOnly = true) public List getStoresByWaitingCount(boolean desc) { - // 1. 모든 waiting:{storeId} key 조회 (패턴 탐색) - Set keys = redisTemplate.keys("waiting:*"); - if (keys == null) return List.of(); - - List result = keys.stream() - .filter(key -> key.startsWith("waiting:") && !key.startsWith("waiting:party:")) - .filter(key -> "zset".equals(redisTemplate.type(key).code())) - .map(key -> { + final String PREFIX = RedisKeyUtils.buildWaitingKeyPrefix(); // 예: "waiting:" + final String UNKNOWN_STORE_NAME = "UNKNOWN"; + + RedisConnection connection = getSafeConnection(); // 안전하게 Redis 커넥션 획득 + + ScanOptions options = ScanOptions.scanOptions() + .match(PREFIX + "[0-9]*") // 숫자로 끝나는 waiting key만 매칭 + .count(1000) + .build(); + + List result = new ArrayList<>(); + + // 반드시 try-with-resources로 Cursor 닫아줘야 함 + try (Cursor cursor = connection.scan(options)) { + while (cursor.hasNext()) { + String key = new String(cursor.next(), StandardCharsets.UTF_8); + + // zset 타입인지 확인 + DataType type = redisTemplate.type(key); + if (type == null || !"zset".equals(type.code())) continue; + Long count = redisTemplate.opsForZSet().zCard(key); - String storeId = key.replace("waiting:", ""); + String storeId = key.replace(PREFIX, ""); + + // DB에서 storeName 조회 String storeName = storeRepository.findById(Long.valueOf(storeId)) .map(Store::getName) - .orElse("UNKNOWN"); - return new StoreWaitingInfo(storeId, storeName, count != null ? count : 0); - }) - .sorted((a, b) -> desc ? - b.getWaitingCount().compareTo(a.getWaitingCount()) : - a.getWaitingCount().compareTo(b.getWaitingCount())) - .toList(); + .orElse(UNKNOWN_STORE_NAME); + + result.add(new StoreWaitingInfo(storeId, storeName, count != null ? count : 0)); + } + } + + // 정렬 로직: 대기 인원 기준 desc/asc + Comparator comparator = Comparator.comparing(StoreWaitingInfo::getWaitingCount); + if (desc) comparator = comparator.reversed(); + result.sort(comparator); return result; } + + private RedisConnection getSafeConnection() { + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + if (factory == null) { + throw new IllegalStateException("RedisConnectionFactory is not configured"); + } + return factory.getConnection(); + } + + } From 1c943debc0e29cc080064f955e080700d2a33506 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 25 Jul 2025 12:13:20 +0900 Subject: [PATCH 284/685] =?UTF-8?q?refactor(Store):=20redis=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 2 +- .../store/service/StoreServiceImpl.java | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index 4a7e6653..40be962e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -29,7 +29,7 @@ public class StoreController { private final StoreService storeService; - @GetMapping("/all-stores/infinite-scroll") + @GetMapping("/all-stores") @Operation( summary = "모든 주점 페이지네이션 조회", description = "모든 주점을 페이지네이션으로 조회합니다." diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 23ba3a83..a606422a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -59,7 +59,12 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page Function.identity(), storeId -> { String key = "waiting:" + storeId; - return redisTemplate.opsForZSet().zCard(key); + try { + return redisTemplate.opsForZSet().zCard(key); + } catch (Exception e) { + return 0L; // Redis 접근 실패 시 0으로 처리 + } + } )); @@ -112,7 +117,12 @@ public StorePageReadDto getStoreByStoreId(Long storeId) { // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 String key = "waiting:" + storeId; - Long waitingSize = redisTemplate.opsForZSet().zCard(key); + Long waitingSize = 0L; + try { + redisTemplate.opsForZSet().zCard(key); + } catch (Exception e) { + waitingSize = 0L; // Redis 접근 실패 시 0으로 처리 + } List images = storeImageRepository.findByStore(store); List imageDto = images.stream() @@ -149,7 +159,11 @@ public List searchByKeywordNative(String keyword) { Function.identity(), storeId -> { String key = "waiting:" + storeId; - return redisTemplate.opsForZSet().zCard(key); + try { + return redisTemplate.opsForZSet().zCard(key); + } catch (Exception e) { + return 0L; // Redis 접근 실패 시 0으로 처리 + } } )); From af7be08aa1b1f6de83f921edc475293a2019e1a7 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 12:24:33 +0900 Subject: [PATCH 285/685] =?UTF-8?q?feat(Store,Reservation):=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EB=A7=8E=EC=9D=80/=EC=A0=81=EC=9D=80=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 최대 5개 store만 호출 - waitingUserRsponse에 reservationId 추가 --- .../service/ReservationService.java | 23 ++++++++++--------- .../store/service/StoreServiceImpl.java | 4 +++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index caa97d81..5a168a9f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; @@ -17,7 +18,6 @@ import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; -import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; @@ -95,21 +95,21 @@ public List getAllWaitingUserDetails(Long storeId) { Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); String status = waitingRedisRepository.getWaitingStatus(storeId, userId); - // 2. DB에서 userName, createdAt 조회 + // 2. DB에서 userName, createdAt, reservationId 조회 String userName = null; LocalDateTime createdAt = null; + Long reservationId = null; - // UserName 조회 (예: UserRepository) - userName = userRepository.findById(Long.valueOf(userId)) - .map(User::getNickname) - .orElse(null); - - // createAt 조회 (예: ReservationRepository) - createdAt = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) - .map(Reservation::getRequestedAt) - .orElse(null); + Optional reservationOpt = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)); + if (reservationOpt.isPresent()) { + Reservation reservation = reservationOpt.get(); + createdAt = reservation.getRequestedAt(); + reservationId = reservation.getId(); + userName = reservation.getUser().getNickname(); + } return new WaitingUserResponse( + reservationId != null ? reservationId.toString() : null, userId, partySize, userName, @@ -119,6 +119,7 @@ public List getAllWaitingUserDetails(Long storeId) { ); }) .toList(); + } // 완료 or 취소 처리된 대기 리스트 조회 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index fcf16fa9..4beb2573 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -243,7 +243,9 @@ public List getStoresByWaitingCount(boolean desc) { if (desc) comparator = comparator.reversed(); result.sort(comparator); - return result; + return result.stream() + .limit(5) + .toList(); } private RedisConnection getSafeConnection() { From 235b4e66641158aab1d54ca699c6e0c637867a6c Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 14:37:49 +0900 Subject: [PATCH 286/685] =?UTF-8?q?feat(Order):=20=EC=98=A4=ED=94=88?= =?UTF-8?q?=EC=8B=9C=EA=B0=84,=20=EA=B3=B5=EC=A7=80=20=EC=B9=BC=EB=9F=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreCreateRequest.java | 2 ++ .../store/dto/StoreCreateResponse.java | 4 ++++ .../applicationadmin/store/dto/StoreReadDto.java | 4 ++++ .../store/dto/StoreUpdateRequest.java | 2 ++ .../store/service/StoreServiceImpl.java | 4 +++- .../nowait/domaincorerdb/store/entity/Store.java | 15 +++++++++++++-- 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java index 4fa8c3e9..4bd9ff23 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java @@ -29,6 +29,8 @@ public Store toEntity() { .name(name) .location(location) .description(description) + .notice("") + .openTime("00002359") .isActive(false) .deleted(false) .build(); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java index cc76f894..bfcac36d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java @@ -18,6 +18,8 @@ public class StoreCreateResponse { private String name; private String location; private String description; + private String notice; + private String openTime; private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; @@ -30,6 +32,8 @@ public static StoreCreateResponse fromEntity(Store store) { .name(store.getName()) .location(store.getLocation()) .description(store.getDescription()) + .notice(store.getNotice()) + .openTime(store.getOpenTime()) .isActive(store.getIsActive()) .deleted(store.getDeleted()) .build(); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java index 68dee6be..5620cd02 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java @@ -19,6 +19,8 @@ public class StoreReadDto { private String name; private String location; private String description; + private String notice; + private String openTime; private StoreImageUploadResponse profileImage; private List bannerImages; private Boolean isActive; @@ -43,6 +45,8 @@ public static StoreReadDto fromEntity(Store store, List Date: Fri, 25 Jul 2025 15:25:38 +0900 Subject: [PATCH 287/685] =?UTF-8?q?refactor(Bookmark):=20Bookmark=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20response=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/service/BookmarkService.java | 18 ++++-- .../store/service/StoreService.java | 1 + .../store/service/StoreServiceImpl.java | 60 ++++++++++++++++++- .../store/repository/StoreRepository.java | 2 + 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 615bf00b..63436967 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -7,10 +7,12 @@ import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.bookmark.dto.BookmarkCreateResponse; -import com.nowait.applicationuser.bookmark.dto.BookmarkGetResponse; +import com.nowait.applicationuser.store.dto.StorePageReadDto; +import com.nowait.applicationuser.store.service.StoreService; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.bookmark.entity.Bookmark; import com.nowait.domainuserrdb.bookmark.repository.BookmarkRepository; @@ -25,6 +27,8 @@ public class BookmarkService { private final BookmarkRepository bookmarkRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; + private final StoreService storeService; + @Transactional public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User customOAuth2User) { parameterValidation(storeId, customOAuth2User); @@ -46,13 +50,17 @@ public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User cust } @Transactional(readOnly = true) - public List getBookmarks(CustomOAuth2User customOAuth2User) { + public List getBookmarks(CustomOAuth2User customOAuth2User) { User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(() -> new EntityNotFoundException("User not found")); - return bookmarkRepository.findAllByUser(user) + .orElseThrow(UserNotFoundException::new); + + List storeIds = bookmarkRepository.findAllByUser(user) .stream() - .map(BookmarkGetResponse::fromEntity) + .map(Bookmark::getStore) + .map(Store::getStoreId) .toList(); + + return storeService.getAllStoresByPageAndDeparments(storeIds); } @Transactional diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 35588fce..e9f79e3b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -20,4 +20,5 @@ public interface StoreService { List getStoresByWaitingCount(boolean desc); + List getAllStoresByPageAndDeparments(List storeIds); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 2144e860..937d706b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -5,7 +5,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -261,4 +260,63 @@ private RedisConnection getSafeConnection() { } + @Override + @Transactional(readOnly = true) + public List getAllStoresByPageAndDeparments(List storeIds) { + // 1) 페이징된 Store 스냅샷 조회 + List stores = storeRepository.findAllByStoreIdInOrderByStoreIdAsc(storeIds); + + // 2) 각 StoreId / Department ID 추출 + List deptIds = stores.stream() + .map(Store::getDepartmentId) + .distinct() + .toList(); + + // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 + Map waitingSizeMap = storeIds.stream() + .collect(Collectors.toMap( + Function.identity(), + storeId -> { + String key = "waiting:" + storeId; + try { + return redisTemplate.opsForZSet().zCard(key); + } catch (Exception e) { + return 0L; // Redis 접근 실패 시 0으로 처리 + } + + } + )); + + // 3) 각 StoreId에 해당하는 이미지 조회 + List allImages = storeImageRepository.findByStore_StoreIdIn(storeIds); + Map> imageMap = allImages.stream() + .map(StoreImageUploadResponse::fromEntity) + .collect(Collectors.groupingBy( + StoreImageUploadResponse::getStoreId + )); + + + // 4) 각 DepartmentId에 해당하는 이름 조회 + List allDepts = departmentRepository.findAllById(deptIds); + Map deptNameMap = allDepts.stream() + .collect(Collectors.toMap( + Department::getId, + Department::getName + )); + + // 5) Dto 매핑 + List content = stores.stream() + .map(store -> { + List imgs = imageMap + .getOrDefault(store.getStoreId(), List.of()); + String departmentName = deptNameMap + .getOrDefault(store.getDepartmentId(), "Unknown Department"); + Long waitingCount = + waitingSizeMap.getOrDefault(store.getStoreId(), 0L); + return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount); + }) + .toList(); + + return content; + } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index 283a1a0e..b5fe61d0 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -34,4 +34,6 @@ OR MATCH(d.name) AGAINST(:kw) """, nativeQuery = true) List searchByKeywordNative(@Param("kw") String booleanKeyword); + + List findAllByStoreIdInOrderByStoreIdAsc(List storeIds); } From 94f0f2b86c6258f01d17591c7ed344ec6c195895 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 15:29:38 +0900 Subject: [PATCH 288/685] =?UTF-8?q?feat(Store):=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A0=95=EB=A0=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - storeImageUrl 추가 - 학과명 추가 --- .../config/security/SecurityConfig.java | 3 ++- .../store/dto/StoreWaitingInfo.java | 6 ++++++ .../store/service/StoreServiceImpl.java | 16 +++++++++++++++- .../store/repository/StoreImageRepository.java | 6 ++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index e9d48ee9..d6aef1c5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -66,7 +66,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/api-docs/**", "/swagger-resources/**", "/webjars/**", - "/demo-ui.html" + "/demo-ui.html", + "/v1/menus//all-menus/stores/**" ) .permitAll() .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java index 56249932..2cdde824 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java @@ -8,6 +8,12 @@ @AllArgsConstructor @Schema(description = "주점 대기 현황 정보 DTO") public class StoreWaitingInfo { + @Schema(description = "배너 첫번째 이미지", example = "https://cdn.gtable.com/profile/user1.jpg") + private String bannerImageUrl; + + @Schema(description = "학과 이름", example = "경영학과") + private String departmentName; + @Schema(description = "주점 ID", example = "1") private String storeId; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 2144e860..d2e9a51e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -26,6 +26,7 @@ import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; @@ -238,7 +239,20 @@ public List getStoresByWaitingCount(boolean desc) { .map(Store::getName) .orElse(UNKNOWN_STORE_NAME); - result.add(new StoreWaitingInfo(storeId, storeName, count != null ? count : 0)); + Store store = storeRepository.findById(Long.valueOf(storeId)) + .orElseThrow(StoreNotFoundException::new); + Department department = departmentRepository.getReferenceById(store.getDepartmentId()); + List storeImageList = storeImageRepository.findByStoreAndImageType(store, ImageType.BANNER); + + String imageUrl = storeImageList.isEmpty() ? null : storeImageList.get(0).getImageUrl(); + + result.add(new StoreWaitingInfo( + imageUrl, + department.getName(), + storeId, + storeName, + count != null ? count : 0 + )); } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java index 45639941..d327fdb0 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java @@ -1,5 +1,7 @@ package com.nowait.domaincorerdb.store.repository; +import static com.nowait.domaincorerdb.store.entity.ImageType.*; + import java.util.List; import java.util.Optional; @@ -18,4 +20,8 @@ public interface StoreImageRepository extends JpaRepository { List findByStore_StoreIdIn(List storeIds); Optional findByStoreStoreIdAndImageType(Long storeId, ImageType imageType); + + List findByStoreAndImageType(Store store, ImageType imageType); + + } From e1debac433c6b8a5bd9139e58fee06258169bc28 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 16:23:53 +0900 Subject: [PATCH 289/685] =?UTF-8?q?fix(Reservation):=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예약 상태 변경 시 날짜만 다른 객체 구분 가능하도록 수정 --- .../service/ReservationService.java | 20 +++++++++++++++---- .../controller/ReservationController.java | 2 -- .../repository/ReservationRepository.java | 4 ++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 5a168a9f..46c73dbb 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -1,6 +1,8 @@ package com.nowait.applicationadmin.reservation.service; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -150,7 +152,7 @@ public CallingWaitingResponseDto callWaiting(Long storeId, String userId, Member .orElse( Reservation.builder() .store(storeRepository.getReferenceById(storeId)) - .user(user) + .user(userRepository.findById(Long.valueOf(userId)).get()) .requestedAt(LocalDateTime.now()) .partySize(partySize) .build() @@ -174,11 +176,21 @@ public String processEntryStatus(Long storeId, String userId, MemberDetails memb throw new ReservationViewUnauthorizedException(); } // 1. DB status 업데이트 - Reservation reservation = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) - .orElseThrow(() -> new IllegalArgumentException("해당 예약이 존재하지 않습니다.")); - reservation.updateStatus(status); + LocalDate today = LocalDate.now(); + LocalDateTime startOfDay = today.atStartOfDay(); + LocalDateTime endOfDay = today.atTime(LocalTime.MAX); + + Reservation reservation = reservationRepository + .findByStore_StoreIdAndUserIdAndRequestedAtBetween( + storeId, + Long.valueOf(userId), + startOfDay, + endOfDay + ) + .orElseThrow(() -> new IllegalArgumentException("오늘 날짜의 예약이 존재하지 않습니다.")); // 2. Redis에서 삭제 waitingRedisRepository.deleteWaiting(storeId, userId); + reservation.updateStatus(status); // 메시지 동적 반환 String action = (status == ReservationStatus.CONFIRMED) ? "입장 완료" : "입장 취소"; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 70899d83..10434638 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -35,8 +35,6 @@ public class ReservationController { private final ReservationService reservationService; @PostMapping("/create/{storeId}") - @Operation(summary = "예약 생성", description = "특정 주점에 대한 예약하기 생성") - @ApiResponse(responseCode = "201", description = "예약 생성") public ResponseEntity create( @PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User, diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index ec39267c..fb64426a 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -1,5 +1,6 @@ package com.nowait.domaincorerdb.reservation.repository; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -18,6 +19,9 @@ public interface ReservationRepository extends JpaRepository Optional findByStore_StoreIdAndUserId(Long storeId, Long userId); + Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween(Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + + List findAllByStore_StoreId(Long storeId); } From a2524b309acc9f66afdf1e85437ca229d4c7a938 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 17:36:41 +0900 Subject: [PATCH 290/685] =?UTF-8?q?fix(Reservation):=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예약 상태 변경 시 날짜만 다른 객체 구분 가능하도록 수정 --- .../controller/ReservationController.java | 33 ++-- .../dto/EntryStatusResponseDto.java | 37 +++++ .../service/ReservationService.java | 145 ++++++++++++------ .../repository/ReservationRepository.java | 7 + 4 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index d0925567..8e134c97 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; +import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.applicationadmin.reservation.service.ReservationService; import com.nowait.common.api.ApiUtils; @@ -51,21 +52,21 @@ public ResponseEntity getCompletedReservationList( return ResponseEntity.ok(response); } - @PatchMapping("/admin/{storeId}/call/{userId}") - @Operation(summary = "예약팀 호출", description = "특정 예약에 대한 호출 진행(호출하는 순간 10분 타임어택)") - @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : WAITING -> CALLING") - public ResponseEntity callWaiting(@PathVariable Long storeId, - @PathVariable String userId, - @AuthenticationPrincipal MemberDetails memberDetails) { - CallingWaitingResponseDto response = reservationService.callWaiting(storeId, userId, memberDetails); - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - response - ) - ); - } + // @PatchMapping("/admin/{storeId}/call/{userId}") + // @Operation(summary = "예약팀 호출", description = "특정 예약에 대한 호출 진행(호출하는 순간 10분 타임어택)") + // @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : WAITING -> CALLING") + // public ResponseEntity callWaiting(@PathVariable Long storeId, + // @PathVariable String userId, + // @AuthenticationPrincipal MemberDetails memberDetails) { + // CallingWaitingResponseDto response = reservationService.callWaiting(storeId, userId, memberDetails); + // return ResponseEntity + // .status(HttpStatus.OK) + // .body( + // ApiUtils.success( + // response + // ) + // ); + // } @PatchMapping("/admin/update/{storeId}/{userId}/{status}") @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") @@ -75,7 +76,7 @@ public ResponseEntity updateEntry( @PathVariable ReservationStatus status, @AuthenticationPrincipal MemberDetails memberDetails ) { - String response = reservationService.processEntryStatus(storeId, userId, memberDetails,status); + EntryStatusResponseDto response = reservationService.processEntryStatus(storeId, userId, memberDetails,status); return ResponseEntity .status(HttpStatus.OK) .body( diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java new file mode 100644 index 00000000..907b924b --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java @@ -0,0 +1,37 @@ +package com.nowait.applicationadmin.reservation.dto; + +import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +// DTO: 호출 시각(calledAt) 또는 완료 메시지(message)를 선택적으로 담습니다. +@Getter +@Builder +public class EntryStatusResponseDto { + @Schema(description = "예약 ID", example = "1201") + private String id; // reservationId + + @Schema(description = "유저 ID", example = "16") + private String userId; + + @Schema(description = "파티 인원", example = "3") + private Integer partySize; + + @Schema(description = "사용자 이름(닉네임)", example = "혜민이") + private String userName; + + @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") + private LocalDateTime createdAt; + + @Schema(description = "대기 상태", example = "CALLING") + private String status; + + @Schema(description = "대기 순번/점수", example = "2.0") + private Double score; + + @Schema(description = "호출 메시지", example = "호출 메시지") + private String message; +} + diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 46c73dbb..1b887b28 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -13,6 +13,7 @@ import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; +import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; @@ -25,6 +26,7 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; @@ -134,69 +136,112 @@ public List getCompletedWaitingUserDetails(Long storeId) { .toList(); } - // 대기 객체 호출 (WAITING -> CALLING) - @Transactional - public CallingWaitingResponseDto callWaiting(Long storeId, String userId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new ReservationViewUnauthorizedException(); - } - String status = waitingRedisRepository.getWaitingStatus(storeId, userId); - if (!"WAITING".equals(status)) { - throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); - } - Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); - waitingRedisRepository.setWaitingStatus(storeId, userId, "CALLING"); - // [2] DB에 상태 영구 저장 (없으면 생성, 있으면 상태만 변경) - Reservation reservation = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) - .orElse( - Reservation.builder() - .store(storeRepository.getReferenceById(storeId)) - .user(userRepository.findById(Long.valueOf(userId)).get()) - .requestedAt(LocalDateTime.now()) - .partySize(partySize) - .build() - ); - reservation.updateStatus(ReservationStatus.CALLING); // setter 대신 빌더로 새 객체 or withStatus 패턴 추천 - reservationRepository.save(reservation); - return CallingWaitingResponseDto.builder() - .storeId(storeId) - .userId(userId) - .status(reservation.getStatus().name()) - .calledAt(reservation.getRequestedAt()) - .build(); - } - // 대기 객체 상태 변경 - @Transactional - public String processEntryStatus(Long storeId, String userId, MemberDetails memberDetails, ReservationStatus status) { - // (권한 체크 필요시 여기에 추가) - User user = userRepository.findById(memberDetails.getId()) + + private User authorize(Long storeId, MemberDetails member) { + User u = userRepository.findById(member.getId()) .orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + if (!Role.SUPER_ADMIN.equals(u.getRole()) && !storeId.equals(u.getStoreId())) { throw new ReservationViewUnauthorizedException(); } - // 1. DB status 업데이트 - LocalDate today = LocalDate.now(); - LocalDateTime startOfDay = today.atStartOfDay(); - LocalDateTime endOfDay = today.atTime(LocalTime.MAX); + return u; + } + + // 공통: 오늘 날짜 예약 조회 + private Reservation findTodayReservation(Long storeId, String userId) { + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); - Reservation reservation = reservationRepository - .findByStore_StoreIdAndUserIdAndRequestedAtBetween( + return reservationRepository + .findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( storeId, Long.valueOf(userId), + List.of(ReservationStatus.WAITING, ReservationStatus.CALLING), startOfDay, endOfDay ) .orElseThrow(() -> new IllegalArgumentException("오늘 날짜의 예약이 존재하지 않습니다.")); - // 2. Redis에서 삭제 - waitingRedisRepository.deleteWaiting(storeId, userId); - reservation.updateStatus(status); + } + + /** + * 상태를 하나의 메서드에서 처리합니다. + * - CALLING : Redis 상태를 CALLING으로 변경 → DB 저장 → calledAt 반환 + * - CONFIRMED : Redis에서 삭제 → DB 저장 → 완료 메시지 반환 + * - CANCELLED : Redis에서 삭제 → DB 저장 → 취소 메시지 반환 + */ + @Transactional + public EntryStatusResponseDto processEntryStatus( + Long storeId, + String userId, + MemberDetails member, + ReservationStatus newStatus + ) { + User manager = authorize(storeId, member); + User user = userRepository.findById(Long.valueOf(userId)).orElseThrow(UserNotFoundException::new); + + String message = null; + Reservation reservation; + + switch (newStatus) { + case CALLING: + // 1) Redis 상태 검사 & 변경 + String curr = waitingRedisRepository.getWaitingStatus(storeId, userId); + if (!ReservationStatus.WAITING.name().equals(curr)) { + throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); + } + waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); - // 메시지 동적 반환 - String action = (status == ReservationStatus.CONFIRMED) ? "입장 완료" : "입장 취소"; - return user.getNickname() + "님의 예약이 " + action + " 처리되었습니다."; + // 2) 파티 인원, 호출 시각 + Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + LocalDateTime now = LocalDateTime.now(); + + // 3) DB에 무조건 새로 저장 + Store store = storeRepository.getReferenceById(storeId); + reservation = Reservation.builder() + .store(store) + .user(user) + .partySize(partySize) + .requestedAt(now) + .status(ReservationStatus.CALLING) + .build(); + reservationRepository.save(reservation); + + break; + + case CONFIRMED: + case CANCELLED: + // 1) Redis에서 제거 + waitingRedisRepository.deleteWaiting(storeId, userId); + + // 2) 오늘 날짜 예약 조회 & 상태 변경 + reservation = findTodayReservation(storeId, userId); + reservation.updateStatus(newStatus); + reservationRepository.save(reservation); + + // 3) 완료/취소 메시지 + message = String.format( + "%s님의 예약이 %s 처리되었습니다.", + user.getNickname(), + newStatus == ReservationStatus.CONFIRMED ? "입장 완료" : "입장 취소" + ); + break; + + default: + throw new IllegalArgumentException("지원하지 않는 상태입니다: " + newStatus); + } + + // 5) 공통 DTO 반환 + return EntryStatusResponseDto.builder() + .id( reservation.getId().toString()) + .userId( userId) + .partySize( reservation.getPartySize()) + .userName( user.getNickname()) + .createdAt( reservation.getRequestedAt()) + .status( reservation.getStatus().name()) + .message( message) + .build(); } + } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index fb64426a..b3f167af 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -21,6 +21,13 @@ public interface ReservationRepository extends JpaRepository Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween(Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + Optional findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( + Long storeId, + Long userId, + List statuses, + LocalDateTime start, + LocalDateTime end + ); List findAllByStore_StoreId(Long storeId); From 5d2d29f63160354c62c8d0649ff658fd5cea9f6f Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Fri, 25 Jul 2025 18:00:19 +0900 Subject: [PATCH 291/685] =?UTF-8?q?feat(Store):=EC=8A=A4=ED=86=A0=EC=96=B4?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=EC=97=AC=EB=B6=80=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 6 ++-- .../store/dto/StorePageReadDto.java | 32 +++++++++++++++++++ .../store/service/StoreService.java | 3 +- .../store/service/StoreServiceImpl.java | 20 +++++++++--- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index 40be962e..e092e31e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,6 +13,7 @@ import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.applicationuser.store.service.StoreService; import com.nowait.common.api.ApiUtils; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -48,12 +50,12 @@ public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable) { @GetMapping("/{storeId}") @Operation(summary = "주점 ID로 주점 상세 조회", description = "특정 주점을 ID로 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 상세 조회 성공") - public ResponseEntity getStoreById(@PathVariable Long storeId) { + public ResponseEntity getStoreById(@PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - storeService.getStoreByStoreId(storeId) + storeService.getStoreByStoreId(storeId, customOAuth2User) ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java index 29cd7194..2d25e23e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java @@ -26,6 +26,7 @@ public class StorePageReadDto { private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; + private Boolean isBookmarked; public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount) { @@ -51,6 +52,37 @@ public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmarked + ) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + + return StorePageReadDto.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .waitingCount(waitingCount) + .departmentId(store.getDepartmentId()) + .departmentName(departmentName) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .profileImage(profile) + .bannerImages(banners) + .isBookmarked(isBookmarked) .build(); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 35588fce..772d0e74 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -9,12 +9,13 @@ import com.nowait.applicationuser.store.dto.StoreReadDto; import com.nowait.applicationuser.store.dto.StoreReadResponse; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; public interface StoreService { StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable); - StorePageReadDto getStoreByStoreId(Long storeId); + StorePageReadDto getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User); List searchByKeywordNative(String name); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index d2e9a51e..08eb23bb 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -5,7 +5,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -17,6 +16,7 @@ import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +33,12 @@ import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; import com.nowait.domaincorerdb.store.repository.StoreImageRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domainuserrdb.bookmark.repository.BookmarkRepository; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import lombok.RequiredArgsConstructor; @@ -45,7 +50,8 @@ public class StoreServiceImpl implements StoreService { private final StoreImageRepository storeImageRepository; private final DepartmentRepository departmentRepository; private final StringRedisTemplate redisTemplate; - + private final BookmarkRepository bookmarkRepository; + private final UserRepository userRepository; @Override @Transactional(readOnly = true) @@ -115,8 +121,10 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Override @Transactional(readOnly = true) - public StorePageReadDto getStoreByStoreId(Long storeId) { + public StorePageReadDto getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User) { if (storeId == null) throw new StoreParamEmptyException(); + User user = userRepository.findById(customOAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -139,7 +147,11 @@ public StorePageReadDto getStoreByStoreId(Long storeId) { .map(StoreImageUploadResponse::fromEntity) .toList(); - return StorePageReadDto.fromEntity(store, imageDto, departmentName, waitingSize); + if (bookmarkRepository.existsByUserAndStore(user, store)) { + return StorePageReadDto.fromEntityWithBookmark(store, imageDto, departmentName, waitingSize, true); + } else { + return StorePageReadDto.fromEntity(store, imageDto, departmentName, waitingSize); + } } @Override From b7da48d770cbe6a5f0c2948bbba87c73822ad1b1 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Fri, 25 Jul 2025 19:45:19 +0900 Subject: [PATCH 292/685] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d1f28a20..5c2e6083 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # GTable 대학교 축제 내 주점을 웨이팅할 수 있는 서비스입니다. +--- +# 아키텍처 +![NoWait-Architecture](https://github.com/user-attachments/assets/143e435d-5ad9-4b3c-a4f7-7db5d117ae44) +--- + # 🍺 주점 웨이팅 시스템 기능 명세 축제 기간 주점 운영을 위한 실시간 웨이팅/관리 시스템 기능 목록입니다. From 5fa073cac604e502d716ce35b7ed92324119e744 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Fri, 25 Jul 2025 19:46:25 +0900 Subject: [PATCH 293/685] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c2e6083..7304092a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,12 @@ 대학교 축제 내 주점을 웨이팅할 수 있는 서비스입니다. --- -# 아키텍처 +# 📌 Architecture +## System Architecture ![NoWait-Architecture](https://github.com/user-attachments/assets/143e435d-5ad9-4b3c-a4f7-7db5d117ae44) + +## Multi Module Architecture + --- # 🍺 주점 웨이팅 시스템 기능 명세 From 74ac2f839ab65c6b9d7cadba7eca26fb5402c8a5 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Fri, 25 Jul 2025 20:27:49 +0900 Subject: [PATCH 294/685] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7304092a..aedb6da0 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,14 @@ --- # 📌 Architecture -## System Architecture +## 1️⃣ System Architecture ![NoWait-Architecture](https://github.com/user-attachments/assets/143e435d-5ad9-4b3c-a4f7-7db5d117ae44) -## Multi Module Architecture +## 2️⃣ Multi Module Architecture +![NoWait-MultiModule-Architecture](https://github.com/user-attachments/assets/e30dcaf6-c1dc-4a5b-b2a3-29e22f1536f1) + +## 3️⃣ ERD +erd --- From 59a767ea5bfbdd8ff4d4aeec227934eb60decccf Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Fri, 25 Jul 2025 20:41:49 +0900 Subject: [PATCH 295/685] Update README.md --- README.md | 140 +++++++++++++++++++----------------------------------- 1 file changed, 50 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index aedb6da0..dc00e88b 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,55 @@ -# GTable -대학교 축제 내 주점을 웨이팅할 수 있는 서비스입니다. - ---- -# 📌 Architecture -## 1️⃣ System Architecture +# NoWait + +## 프로젝트 소개 +주점 대기 예약부터 메뉴 주문까지 원격으로 처리할 수 있는 웨이팅 서비스입니다. + +## 프로젝트 목표 + +### 핵심 목표 +- 트래픽이 몰려도 안정적인 대기 및 주문 시스템 설계 + +## 👨‍👩‍👧‍👦 팀원 소개 + + + + + + + + + + + + + +
BackendBackend
김지훈정혜민
+ + + +
+ +## 사용 기술 + +
+ + + + + +
+ + + + +
+
+ +## 📌 Architecture +### 1️⃣ System Architecture ![NoWait-Architecture](https://github.com/user-attachments/assets/143e435d-5ad9-4b3c-a4f7-7db5d117ae44) -## 2️⃣ Multi Module Architecture +### 2️⃣ Multi Module Architecture ![NoWait-MultiModule-Architecture](https://github.com/user-attachments/assets/e30dcaf6-c1dc-4a5b-b2a3-29e22f1536f1) -## 3️⃣ ERD +### 3️⃣ ERD erd - ---- - -# 🍺 주점 웨이팅 시스템 기능 명세 - -축제 기간 주점 운영을 위한 실시간 웨이팅/관리 시스템 기능 목록입니다. -아래는 사용자 및 관리자 기능을 기준으로 정리한 전체 기능 목록입니다. - - ---- - -## 👤 사용자 기능 - -### 1. 주점 목록 조회 -- 웨이팅 인원이 적은 순으로 정렬된 주점 목록을 노출 -- 매장명, 위치, 오픈 여부, 대표 이미지 제공 - -### 2. 주점 검색 -- 키워드로 주점 이름 및 설명에서 검색 -- 검색 결과가 없을 경우 안내 문구 노출 - -### 3. 주점 상세 조회 -- 매장 이미지, 설명, 위치, 영업 상태 확인 -- 영업 중일 경우 웨이팅 신청 버튼 활성화 - -### 4. 접속 대기열 페이지 -- 접속 폭주 시 진입 -- 현재 대기 인원 수 및 나의 대기 번호 확인 가능 - -### 5. 메뉴 조회 -- 주점에 등록된 메뉴 목록 확인 -- 메뉴 이름, 가격, 이미지 제공 -- 품절 메뉴는 뱃지 또는 비활성화 처리 - ---- - -## 🛠 관리자 기능 - -### 6. 주점 등록 -- 주점명, 썸네일 이미지, 위치, 설명 입력 -- 관리자당 1개 주점만 등록 가능 - -### 7. 주점 수정 -- 주점 이미지, 이름, 위치, 설명 개별 수정 -- 이미지 5개 이하 등록 가능 (파일 제한 있음) - -### 8. 주점 삭제 -- 등록된 주점을 삭제하며 관련 웨이팅도 함께 제거 -- 삭제 전 확인 팝업 제공 - -### 9. 메뉴 등록 -- 메뉴 이름, 설명, 가격, 이미지(1장), 품절 여부 입력 -- 등록 성공 시 목록에 반영 - -### 10. 메뉴 수정 -- 이름, 설명, 가격, 이미지 각각 수정 가능 - -### 11. 메뉴 삭제 -- 특정 메뉴를 삭제 -- 삭제 확인 팝업 포함 - -### 12. 주문 내역 확인 -- 사용자 주문 목록을 시간순으로 조회 -- 주문 → 조리중 → 완료 상태 전환 가능 -- 수락 시 “입금 확인” 경고 팝업 제공 - -### 13. 웨이팅 목록 확인 -- 해당 주점의 웨이팅 사용자 목록을 무한스크롤 방식으로 조회 -- 호출, 입장, 취소 등 액션 버튼 포함 - -### 14. 웨이팅 호출 -- 호출 시 카카오톡 알림 전송 -- 동일 사용자 중복 호출 방지 - -### 15. 웨이팅 입장 처리 -- 호출된 사용자를 입장 완료 처리 -- 웨이팅 목록에서 제거 후 ‘입장 완료’ 처리 - -### 16. 웨이팅 취소 처리 -- 노쇼 또는 미입장 사용자를 취소 처리 -- 알림톡 전송 및 목록에서 제거 - From 6793e1805a223fa445a8d93a081485be8cb9357e Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Fri, 25 Jul 2025 20:42:52 +0900 Subject: [PATCH 296/685] Update README.md --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dc00e88b..9497e46b 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,30 @@ ## 👨‍👩‍👧‍👦 팀원 소개 - - + + - - + +
BackendBackendBackendBackend
김지훈정혜민 + 김지훈 + + 정혜민 +
- + 김지훈 프로필 - + 정혜민 프로필
+ ## 사용 기술
From 443205a8e75e8275b73604e8b09a548389217f14 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Fri, 25 Jul 2025 20:45:38 +0900 Subject: [PATCH 297/685] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9497e46b..002c0696 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # NoWait +Logo-primary ## 프로젝트 소개 주점 대기 예약부터 메뉴 주문까지 원격으로 처리할 수 있는 웨이팅 서비스입니다. @@ -24,11 +25,11 @@ - 김지훈 프로필 - 정혜민 프로필 From f8fcc118030b299d89548178ec330b70abd718ae Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 25 Jul 2025 21:02:16 +0900 Subject: [PATCH 298/685] =?UTF-8?q?refactor(Menu):=20Menu=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B6=8C=ED=95=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/config/security/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index e9d48ee9..8bd4f0f7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -59,6 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/oauth2/authorization/kakao", // 카카오 로그인 요청 "/login/oauth2/code/**", // 카카오 인증 콜백 "/api/refresh-token", // refresh token (토큰 갱신) + "/v1/menus/**", // 모든 메뉴 조회 "/orders/**", "/swagger-ui/**", "/v3/api-docs/**", From 5ef8c7357e5a4673221a50a8be80ca98c963005f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 25 Jul 2025 21:15:35 +0900 Subject: [PATCH 299/685] =?UTF-8?q?refactor(Store):=20notice,=20openTime?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/store/dto/StorePageReadDto.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java index 2d25e23e..bae989be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java @@ -21,6 +21,8 @@ public class StorePageReadDto { private String name; private String location; private String description; + private String notice; + private String openTime; private StoreImageUploadResponse profileImage; private List bannerImages; private Boolean isActive; @@ -48,6 +50,8 @@ public static StorePageReadDto fromEntity(Store store, List Date: Fri, 25 Jul 2025 21:36:22 +0900 Subject: [PATCH 300/685] =?UTF-8?q?refactor(Store):=20Store=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?StoreDetailReadResponse=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreDetailReadResponse.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java new file mode 100644 index 00000000..3ccf1674 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java @@ -0,0 +1,97 @@ +package com.nowait.applicationuser.store.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.nowait.domaincorerdb.store.entity.ImageType; +import com.nowait.domaincorerdb.store.entity.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreDetailReadResponse { + private Long storeId; + private Long waitingCount; + private Boolean isWaiting; + private Long departmentId; + private String departmentName; + private String name; + private String location; + private String description; + private String notice; + private String openTime; + private StoreImageUploadResponse profileImage; + private List bannerImages; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + private Boolean isBookmarked; + + public static StoreDetailReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isWaiting) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + + return StoreDetailReadResponse.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .waitingCount(waitingCount) + .isWaiting(isWaiting) + .departmentId(store.getDepartmentId()) + .departmentName(departmentName) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .notice(store.getNotice()) + .openTime(store.getOpenTime()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .profileImage(profile) + .bannerImages(banners) + .isBookmarked(false) + .build(); + } + + public static StoreDetailReadResponse fromEntityWithBookmark( + Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmarked, Boolean isWaiting + ) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + + return StoreDetailReadResponse.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .waitingCount(waitingCount) + .isWaiting(isWaiting) + .departmentId(store.getDepartmentId()) + .departmentName(departmentName) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .notice(store.getNotice()) + .openTime(store.getOpenTime()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .profileImage(profile) + .bannerImages(banners) + .isBookmarked(isBookmarked) + .build(); + } +} From f29905194417b4e0c11e61b11ea2856621ed206a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 25 Jul 2025 21:36:35 +0900 Subject: [PATCH 301/685] =?UTF-8?q?refactor(Store):=20isWaiting=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingUserRedisRepository.java | 12 +++++++++++- .../store/service/StoreService.java | 3 ++- .../store/service/StoreServiceImpl.java | 17 ++++++++--------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 2b74fd1d..16c84901 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -35,17 +35,20 @@ public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, } return Boolean.TRUE.equals(added); } + // 예약한 사람이 등록한 동반인원(partySize) 조회 public Integer getPartySize(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; Object value = redisTemplate.opsForHash().get(partyKey, userId); return Integer.valueOf(value.toString()); } + // 예약자 대기순위 조회 public Long getRank(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; return redisTemplate.opsForZSet().rank(key, userId); } + // 예약 취소 public boolean removeWaiting(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; @@ -56,6 +59,7 @@ public boolean removeWaiting(Long storeId, String userId) { redisTemplate.opsForHash().delete(statusKey, userId); return true; } + // 예약 등록 시간 public Long getWaitingTimestamp(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; @@ -67,7 +71,8 @@ public Long getWaitingTimestamp(Long storeId, String userId) { public List getUserWaitingStoreIds(String userId) { // key pattern으로 모든 매장 대기열 조회 (keys: waiting:*) Set keys = redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*"); - if (keys == null) return List.of(); + if (keys == null) + return List.of(); List result = new ArrayList<>(); for (String key : keys) { @@ -84,6 +89,7 @@ public List getUserWaitingStoreIds(String userId) { } return result; } + // 상태값 조회 public String getWaitingStatus(Long storeId, String userId) { String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; @@ -91,6 +97,10 @@ public String getWaitingStatus(Long storeId, String userId) { return value == null ? null : value.toString(); } + // 사용자가 해당 스토어 대기열에 있는지 여부 반환 + public boolean isUserWaiting(Long storeId, String userId) { + return getRank(storeId, userId) != null; + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 2f7cdab1..d7b450d7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Pageable; import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; +import com.nowait.applicationuser.store.dto.StoreDetailReadResponse; import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StoreReadDto; import com.nowait.applicationuser.store.dto.StoreReadResponse; @@ -15,7 +16,7 @@ public interface StoreService { StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable); - StorePageReadDto getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User); + StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User); List searchByKeywordNative(String name); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index ec246e03..408c18d3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -20,7 +20,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; +import com.nowait.applicationuser.store.dto.StoreDetailReadResponse; import com.nowait.applicationuser.store.dto.StoreImageUploadResponse; import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; @@ -52,6 +54,7 @@ public class StoreServiceImpl implements StoreService { private final StringRedisTemplate redisTemplate; private final BookmarkRepository bookmarkRepository; private final UserRepository userRepository; + private final WaitingUserRedisRepository waitingRepo; @Override @Transactional(readOnly = true) @@ -121,7 +124,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Override @Transactional(readOnly = true) - public StorePageReadDto getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User) { + public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User) { if (storeId == null) throw new StoreParamEmptyException(); User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); @@ -135,12 +138,8 @@ public StorePageReadDto getStoreByStoreId(Long storeId, CustomOAuth2User customO // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 String key = "waiting:" + storeId; - Long waitingSize = 0L; - try { - redisTemplate.opsForZSet().zCard(key); - } catch (Exception e) { - waitingSize = 0L; // Redis 접근 실패 시 0으로 처리 - } + long waitingSize = redisTemplate.opsForZSet().zCard(key); + boolean userWaiting = waitingRepo.isUserWaiting(storeId, String.valueOf(customOAuth2User.getUserId())); List images = storeImageRepository.findByStore(store); List imageDto = images.stream() @@ -148,9 +147,9 @@ public StorePageReadDto getStoreByStoreId(Long storeId, CustomOAuth2User customO .toList(); if (bookmarkRepository.existsByUserAndStore(user, store)) { - return StorePageReadDto.fromEntityWithBookmark(store, imageDto, departmentName, waitingSize, true); + return StoreDetailReadResponse.fromEntityWithBookmark(store, imageDto, departmentName, waitingSize, true, userWaiting); } else { - return StorePageReadDto.fromEntity(store, imageDto, departmentName, waitingSize); + return StoreDetailReadResponse.fromEntity(store, imageDto, departmentName, waitingSize, userWaiting); } } From ffeb4c2bf59e7de00b6b480e422bb2eb0684b8c0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 26 Jul 2025 01:33:25 +0900 Subject: [PATCH 302/685] =?UTF-8?q?refactor(MenuImage):=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuImageController.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java index 5e5b6d20..5d6add61 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java @@ -47,7 +47,16 @@ public ResponseEntity uploadMenuImage( } MenuImageUploadResponse response = menuImageService.save(menuId, file); - return ResponseEntity.status(HttpStatus.CREATED).body(ApiUtils.success(response)); + return ResponseEntity + .status( + HttpStatus.CREATED + ) + .body( + ApiUtils + .success( + response + ) + ); } @DeleteMapping("/images/{menuImageId}") From f0da7edf53fc2a7bdebcc5eb5c666f1aba2a54f3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 26 Jul 2025 01:33:49 +0900 Subject: [PATCH 303/685] =?UTF-8?q?refactor(StoreImage):=20type=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/store/service/StoreImageService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java index 772acdcf..fb7c8388 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java @@ -36,7 +36,7 @@ public List saveAll(Long storeId, List if (files == null || files.isEmpty()) throw new StoreImageEmptyException(); - String type = "store"; + String type = "banner"; Store store = storeRepository.findById(storeId) .orElseThrow(StoreNotFoundException::new); @@ -76,7 +76,7 @@ public List saveAll(Long storeId, List @Transactional public StoreImageUploadResponse saveProfileImage(Long storeId, MultipartFile file) { - String type = "store"; + String type = "profile"; Store store = storeRepository.findById(storeId) .orElseThrow(StoreNotFoundException::new); From c99178e9bc111181d5fee918e7c07859f3f354f0 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Sat, 26 Jul 2025 02:29:07 +0900 Subject: [PATCH 304/685] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 002c0696..acc58643 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # NoWait Logo-primary + ## 프로젝트 소개 주점 대기 예약부터 메뉴 주문까지 원격으로 처리할 수 있는 웨이팅 서비스입니다. @@ -54,7 +55,7 @@ ## 📌 Architecture ### 1️⃣ System Architecture -![NoWait-Architecture](https://github.com/user-attachments/assets/143e435d-5ad9-4b3c-a4f7-7db5d117ae44) +![NoWait-Architecture](https://github.com/user-attachments/assets/b85dc5fd-0a42-4f1a-a79e-448044a08c47) ### 2️⃣ Multi Module Architecture ![NoWait-MultiModule-Architecture](https://github.com/user-attachments/assets/e30dcaf6-c1dc-4a5b-b2a3-29e22f1536f1) From ff98ed5164d660320d146555fed6c14ae41726a3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 26 Jul 2025 02:30:39 +0900 Subject: [PATCH 305/685] =?UTF-8?q?refactor(s3):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20resize=20=EC=A3=BC=EC=86=8C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/service/MenuImageService.java | 2 +- .../controller/StoreImageController.java | 4 +-- .../store/service/StoreImageService.java | 4 +-- .../com/nowait/infraaws/aws/s3/S3Service.java | 25 +++++++++++++------ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java index 10c60ac8..e1bdcc5c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java @@ -38,7 +38,7 @@ public MenuImageUploadResponse save(Long menuId, MultipartFile file) { // MenuImage 엔티티 생성 및 저장 MenuImage menuImage = MenuImage.builder() .menu(menu) - .imageUrl(uploadResult.url()) + .imageUrl(uploadResult.resizedUrl()) .fileKey(uploadResult.key()) .build(); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java index 30e4202c..d65bef08 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/controller/StoreImageController.java @@ -81,8 +81,8 @@ public ResponseEntity uploadStoreProfileImage( description = "주점 이미지를 삭제합니다. 이미지 ID를 사용하여 특정 이미지를 삭제할 수 있습니다." ) @ApiResponse(responseCode = "200", description = "주점 이미지 삭제 성공") - public ResponseEntity deleteStoreImage(@PathVariable Long imageId) { - storeImageService.delete(imageId); + public ResponseEntity deleteStoreImage(@PathVariable Long storeImageId) { + storeImageService.delete(storeImageId); return ResponseEntity .status(HttpStatus.OK) .body( diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java index fb7c8388..1aa7b754 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java @@ -61,7 +61,7 @@ public List saveAll(Long storeId, List for (S3Service.S3UploadResult uploadResult : uploadResults) { StoreImage storeImage = StoreImage.builder() .store(store) - .imageUrl(uploadResult.url()) + .imageUrl(uploadResult.resizedUrl()) .fileKey(uploadResult.key()) .imageType(ImageType.BANNER) .build(); @@ -93,7 +93,7 @@ public StoreImageUploadResponse saveProfileImage(Long storeId, MultipartFile fil // StoreImage 엔티티 생성 및 저장 StoreImage storeImage = StoreImage.builder() .store(store) - .imageUrl(uploadResult.url()) + .imageUrl(uploadResult.resizedUrl()) .fileKey(uploadResult.key()) .imageType(ImageType.PROFILE) .build(); diff --git a/nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java b/nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java index 18416c32..604d06b4 100644 --- a/nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java +++ b/nowait-infra/src/main/java/com/nowait/infraaws/aws/s3/S3Service.java @@ -21,31 +21,40 @@ public class S3Service { private final AmazonS3Client amazonS3Client; @Value("${cloud.aws.s3.bucket}") - private String bucket; + private String originalBucket; - public record S3UploadResult(String key, String url) { + @Value("${cloud.aws.s3.resize-bucket}") + private String resizeBucket; + + public record S3UploadResult(String key, String originalUrl, String resizedUrl) { } @Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL) @Async("s3UploadExecutor") - public CompletableFuture upload(String type, Long refId, MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) + public CompletableFuture upload(String type, Long refId, + MultipartFile file) { // TODO MultipartFile 분리 필요 (Spring에 의존하면 안 됨) try (InputStream inputStream = file.getInputStream()) { String key = createFileKey(type, refId, file.getOriginalFilename()); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(file.getSize()); - amazonS3Client.putObject(bucket, key, inputStream, metadata); - String url = amazonS3Client.getUrl(bucket, key).toString(); + // 1) 원본 버킷에 이미지 업로드 + amazonS3Client.putObject(originalBucket, key, inputStream, metadata); + + // 2) 각 버킷의 URL 생성 + String originalUrl = amazonS3Client.getUrl(originalBucket, key).toString(); + String resizedUrl = amazonS3Client.getUrl(resizeBucket, key).toString(); - return CompletableFuture.completedFuture(new S3UploadResult(key, url)); + return CompletableFuture.completedFuture(new S3UploadResult(key, originalUrl, resizedUrl)); } catch (Exception e) { throw new RuntimeException("S3 업로드 실패", e); } } - public void delete(String filename) { + public void delete(String key) { try { - amazonS3Client.deleteObject(bucket, filename); + amazonS3Client.deleteObject(originalBucket, key); + amazonS3Client.deleteObject(resizeBucket, key); } catch (Exception e) { throw new RuntimeException("S3 파일 삭제 실패", e); } From a6a4eec8fb2a77b5225fd1e22d112745672e776c Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Sat, 26 Jul 2025 02:36:41 +0900 Subject: [PATCH 306/685] Update README.md --- README.md | 58 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index acc58643..f78dc45b 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,40 @@ # NoWait Logo-primary - -## 프로젝트 소개 +## 💡프로젝트 소개 주점 대기 예약부터 메뉴 주문까지 원격으로 처리할 수 있는 웨이팅 서비스입니다. -## 프로젝트 목표 +## 📜 프로젝트 목표 ### 핵심 목표 - 트래픽이 몰려도 안정적인 대기 및 주문 시스템 설계 +## 📌 Architecture +### 1️⃣ System Architecture +![NoWait-Architecture](https://github.com/user-attachments/assets/b85dc5fd-0a42-4f1a-a79e-448044a08c47) + +### 2️⃣ Multi Module Architecture +![NoWait-MultiModule-Architecture](https://github.com/user-attachments/assets/e30dcaf6-c1dc-4a5b-b2a3-29e22f1536f1) + +### 3️⃣ ERD +erd + +## 🛠️ 사용 기술 + +
+ + + + + +
+ + + + +
+
+ ## 👨‍👩‍👧‍👦 팀원 소개 @@ -35,30 +60,3 @@
- - -## 사용 기술 - -
- - - - - -
- - - - -
-
- -## 📌 Architecture -### 1️⃣ System Architecture -![NoWait-Architecture](https://github.com/user-attachments/assets/b85dc5fd-0a42-4f1a-a79e-448044a08c47) - -### 2️⃣ Multi Module Architecture -![NoWait-MultiModule-Architecture](https://github.com/user-attachments/assets/e30dcaf6-c1dc-4a5b-b2a3-29e22f1536f1) - -### 3️⃣ ERD -erd From b463eb1c84d372acec2d1672896b4e3543445700 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Sat, 26 Jul 2025 02:46:31 +0900 Subject: [PATCH 307/685] Update README.md --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f78dc45b..8ecd928b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,10 @@ ## 📜 프로젝트 목표 ### 핵심 목표 -- 트래픽이 몰려도 안정적인 대기 및 주문 시스템 설계 +- Redis SortedSet기반 대기열으로 급격한 트래픽에도 서비스 중단 없이 대기열 유지 +- Redis SPOF 해결 +- 멀티 모듈 프로젝트 구조로 서비스 영역을 분리하여 독립 빌드·배포 수행 + ## 📌 Architecture ### 1️⃣ System Architecture @@ -19,6 +22,14 @@ ### 3️⃣ ERD erd +## 🔌 API 문서 +### 관리자 +Admin Server Swagger + +### 사용자 +User Server Swagger + + ## 🛠️ 사용 기술
@@ -52,11 +63,11 @@ 김지훈 프로필 + width="200" height="200"> 정혜민 프로필 + width="200" height="200"> From 27ab28831a38f9dbf38b7004d9bcfc72f462176e Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Sat, 26 Jul 2025 14:26:32 +0900 Subject: [PATCH 308/685] =?UTF-8?q?feat(Reservation):=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 2 +- .../service/ReservationService.java | 27 ++++++++++++++----- .../repository/ReservationRepository.java | 5 +++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index 8e134c97..f6d91d36 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -36,7 +36,7 @@ public class ReservationController { @GetMapping("/admin/{storeId}/waiting/users") @Operation(summary = "주점별 전체 대기/호출중 리스트 조회", description = "주점에 대한 대기 리스트 조회(WAITING,CALLING)") @ApiResponse(responseCode = "200", description = "주점별 전체 대기 리스트 조회") - public ResponseEntity> getWaitingUsersWithScore(@PathVariable Long storeId) { + public ResponseEntity getWaitingUsersWithScore(@PathVariable Long storeId) { List response = reservationService.getAllWaitingUserDetails(storeId); return ResponseEntity.ok(response); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 1b887b28..e0177ccf 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; @@ -90,6 +92,7 @@ public CallGetResponseDto updateReservationStatus(Long reservationId, Reservatio @Transactional(readOnly = true) public List getAllWaitingUserDetails(Long storeId) { List> waitingList = waitingRedisRepository.getAllWaitingWithScore(storeId); + System.out.println(waitingList); return waitingList.stream() .map(tuple -> { @@ -100,18 +103,24 @@ public List getAllWaitingUserDetails(Long storeId) { String status = waitingRedisRepository.getWaitingStatus(storeId, userId); // 2. DB에서 userName, createdAt, reservationId 조회 - String userName = null; - LocalDateTime createdAt = null; - Long reservationId = null; - - Optional reservationOpt = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)); + //TODO 개선필요 -> createAt 정확성 및 reservationhId 생성 방법(예약생성부터 DB에 박아야하나....) + String userName = userRepository.getReferenceById(Long.valueOf(userId)).getNickname(); + LocalDateTime createdAt = LocalDateTime.now(); + String reservationId = String.valueOf( + ThreadLocalRandom.current().nextInt(1, 100)); + + Optional reservationOpt = reservationRepository.findByStore_StoreIdAndUserIdAndRequestedAtBetween( + storeId, Long.valueOf(userId), LocalDate.now().atStartOfDay(), LocalDate.now().atTime(LocalTime.MAX)); if (reservationOpt.isPresent()) { Reservation reservation = reservationOpt.get(); createdAt = reservation.getRequestedAt(); - reservationId = reservation.getId(); + reservationId = reservation.getId().toString(); userName = reservation.getUser().getNickname(); + } else { } + + return new WaitingUserResponse( reservationId != null ? reservationId.toString() : null, userId, @@ -129,7 +138,11 @@ public List getAllWaitingUserDetails(Long storeId) { // 완료 or 취소 처리된 대기 리스트 조회 @Transactional(readOnly = true) public List getCompletedWaitingUserDetails(Long storeId) { - List reservations = reservationRepository.findAllByStore_StoreId(storeId); + List reservations = reservationRepository.findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( + storeId, + List.of(ReservationStatus.CONFIRMED, ReservationStatus.CANCELLED), + LocalDate.now().atStartOfDay(), + LocalDate.now().atTime(LocalTime.MAX)); return reservations.stream() .map(r -> WaitingUserResponse.fromEntity(r)) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index b3f167af..96c7639f 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -19,8 +19,11 @@ public interface ReservationRepository extends JpaRepository Optional findByStore_StoreIdAndUserId(Long storeId, Long userId); - Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween(Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween( + Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + List findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( + Long storeId, List statuses, LocalDateTime start, LocalDateTime end); Optional findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( Long storeId, Long userId, From bdf3e5eec8540431f50f17ac58ddce97c699f662 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 26 Jul 2025 23:38:09 +0900 Subject: [PATCH 309/685] =?UTF-8?q?refactor(Reservation):=20NonUniqueResul?= =?UTF-8?q?tException=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 3 ++- .../reservation/repository/ReservationRepository.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index e0177ccf..abae93f4 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -94,6 +94,7 @@ public List getAllWaitingUserDetails(Long storeId) { List> waitingList = waitingRedisRepository.getAllWaitingWithScore(storeId); System.out.println(waitingList); + // TODO N + 1 발생 -> 개선 필요 return waitingList.stream() .map(tuple -> { String userId = tuple.getValue(); @@ -109,7 +110,7 @@ public List getAllWaitingUserDetails(Long storeId) { String reservationId = String.valueOf( ThreadLocalRandom.current().nextInt(1, 100)); - Optional reservationOpt = reservationRepository.findByStore_StoreIdAndUserIdAndRequestedAtBetween( + Optional reservationOpt = reservationRepository.findFirstByStore_StoreIdAndUserIdAndRequestedAtBetweenOrderByRequestedAtDesc( storeId, Long.valueOf(userId), LocalDate.now().atStartOfDay(), LocalDate.now().atTime(LocalTime.MAX)); if (reservationOpt.isPresent()) { Reservation reservation = reservationOpt.get(); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 96c7639f..f3e74b38 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -22,6 +22,9 @@ public interface ReservationRepository extends JpaRepository Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween( Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + Optional findFirstByStore_StoreIdAndUserIdAndRequestedAtBetweenOrderByRequestedAtDesc( + Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + List findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( Long storeId, List statuses, LocalDateTime start, LocalDateTime end); Optional findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( From 87c79078fab41fc1b11cfbfdf0f9def4ced6f6c8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 27 Jul 2025 01:04:01 +0900 Subject: [PATCH 310/685] =?UTF-8?q?fix(Reservation):=20=EC=A3=BC=EB=AC=B8?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20MismatchedInputException=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 9 +-- .../dto/ReservationStatusRequest.java | 14 +++++ .../service/ReservationService.java | 63 ++++++++++--------- .../common/enums/ReservationStatus.java | 2 +- 4 files changed, 54 insertions(+), 34 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusRequest.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index f6d91d36..5133d2b9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -9,11 +9,13 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; +import com.nowait.applicationadmin.reservation.dto.ReservationStatusRequest; import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.applicationadmin.reservation.service.ReservationService; import com.nowait.common.api.ApiUtils; @@ -67,16 +69,16 @@ public ResponseEntity getCompletedReservationList( // ) // ); // } - @PatchMapping("/admin/update/{storeId}/{userId}/{status}") + @PatchMapping("/admin/update/{storeId}/{userId}") @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") public ResponseEntity updateEntry( @PathVariable Long storeId, @PathVariable String userId, - @PathVariable ReservationStatus status, + @RequestBody ReservationStatusRequest request, @AuthenticationPrincipal MemberDetails memberDetails ) { - EntryStatusResponseDto response = reservationService.processEntryStatus(storeId, userId, memberDetails,status); + EntryStatusResponseDto response = reservationService.processEntryStatus(storeId, userId, memberDetails, request.getStatus()); return ResponseEntity .status(HttpStatus.OK) .body( @@ -85,5 +87,4 @@ public ResponseEntity updateEntry( )); } - } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusRequest.java new file mode 100644 index 00000000..9508a8e8 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationStatusRequest.java @@ -0,0 +1,14 @@ +package com.nowait.applicationadmin.reservation.dto; + +import com.nowait.common.enums.ReservationStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ReservationStatusRequest { + private ReservationStatus status; +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index abae93f4..b8dfd999 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -45,10 +45,11 @@ public class ReservationService { private final UserRepository userRepository; private final WaitingRedisRepository waitingRedisRepository; private final StoreRepository storeRepository; + //TODO 성능 비교를 위해 남겨둔 로직 @Transactional(readOnly = true) public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new ReservationViewUnauthorizedException(); } @@ -61,10 +62,14 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem int callingCount = 0; List reservationDtoList = new ArrayList<>(); for (Reservation r : reservations) { - if (r.getStatus() == ReservationStatus.WAITING) waitingCount++; - if (r.getStatus() == ReservationStatus.CONFIRMED) confirmedCount++; - if (r.getStatus() == ReservationStatus.CANCELLED) cancelledCount++; - if (r.getStatus() == ReservationStatus.CALLING) callingCount++; + if (r.getStatus() == ReservationStatus.WAITING) + waitingCount++; + if (r.getStatus() == ReservationStatus.CONFIRMED) + confirmedCount++; + if (r.getStatus() == ReservationStatus.CANCELLED) + cancelledCount++; + if (r.getStatus() == ReservationStatus.CALLING) + callingCount++; reservationDtoList.add(ReservationGetResponseDto.fromEntity(r)); } @@ -76,18 +81,22 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem .reservationList(reservationDtoList) .build(); } + //TODO 성능 비교를 위해 남겨둔 로직 @Transactional public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Reservation reservation = reservationRepository.findById(reservationId).orElseThrow(ReservationNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(reservation.getStore().getStoreId())) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + Reservation reservation = reservationRepository.findById(reservationId) + .orElseThrow(ReservationNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId() + .equals(reservation.getStore().getStoreId())) { throw new ReservationUpdateUnauthorizedException(); } - reservation.updateStatus(requestDto.getStatus()); + reservation.updateStatus(requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } + // Redis queue에 있는 주점별 전체 대기열 조회 @Transactional(readOnly = true) public List getAllWaitingUserDetails(Long storeId) { @@ -111,7 +120,8 @@ public List getAllWaitingUserDetails(Long storeId) { ThreadLocalRandom.current().nextInt(1, 100)); Optional reservationOpt = reservationRepository.findFirstByStore_StoreIdAndUserIdAndRequestedAtBetweenOrderByRequestedAtDesc( - storeId, Long.valueOf(userId), LocalDate.now().atStartOfDay(), LocalDate.now().atTime(LocalTime.MAX)); + storeId, Long.valueOf(userId), LocalDate.now().atStartOfDay(), + LocalDate.now().atTime(LocalTime.MAX)); if (reservationOpt.isPresent()) { Reservation reservation = reservationOpt.get(); createdAt = reservation.getRequestedAt(); @@ -120,8 +130,6 @@ public List getAllWaitingUserDetails(Long storeId) { } else { } - - return new WaitingUserResponse( reservationId != null ? reservationId.toString() : null, userId, @@ -150,7 +158,6 @@ public List getCompletedWaitingUserDetails(Long storeId) { .toList(); } - private User authorize(Long storeId, MemberDetails member) { User u = userRepository.findById(member.getId()) .orElseThrow(UserNotFoundException::new); @@ -163,7 +170,7 @@ private User authorize(Long storeId, MemberDetails member) { // 공통: 오늘 날짜 예약 조회 private Reservation findTodayReservation(Long storeId, String userId) { LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); + LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); return reservationRepository .findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( @@ -184,16 +191,16 @@ private Reservation findTodayReservation(Long storeId, String userId) { */ @Transactional public EntryStatusResponseDto processEntryStatus( - Long storeId, - String userId, - MemberDetails member, + Long storeId, + String userId, + MemberDetails member, ReservationStatus newStatus ) { User manager = authorize(storeId, member); User user = userRepository.findById(Long.valueOf(userId)).orElseThrow(UserNotFoundException::new); - String message = null; - Reservation reservation; + String message = null; + Reservation reservation; switch (newStatus) { case CALLING: @@ -206,7 +213,7 @@ public EntryStatusResponseDto processEntryStatus( // 2) 파티 인원, 호출 시각 Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(); // 3) DB에 무조건 새로 저장 Store store = storeRepository.getReferenceById(storeId); @@ -245,17 +252,15 @@ public EntryStatusResponseDto processEntryStatus( // 5) 공통 DTO 반환 return EntryStatusResponseDto.builder() - .id( reservation.getId().toString()) - .userId( userId) - .partySize( reservation.getPartySize()) - .userName( user.getNickname()) - .createdAt( reservation.getRequestedAt()) - .status( reservation.getStatus().name()) - .message( message) + .id(reservation.getId().toString()) + .userId(userId) + .partySize(reservation.getPartySize()) + .userName(user.getNickname()) + .createdAt(reservation.getRequestedAt()) + .status(reservation.getStatus().name()) + .message(message) .build(); } - - } diff --git a/nowait-common/src/main/java/com/nowait/common/enums/ReservationStatus.java b/nowait-common/src/main/java/com/nowait/common/enums/ReservationStatus.java index 4d4f58a2..8ede6dd2 100644 --- a/nowait-common/src/main/java/com/nowait/common/enums/ReservationStatus.java +++ b/nowait-common/src/main/java/com/nowait/common/enums/ReservationStatus.java @@ -1,5 +1,5 @@ package com.nowait.common.enums; public enum ReservationStatus { - WAITING,CALLING, CONFIRMED, CANCELLED + WAITING, CALLING, CONFIRMED, CANCELLED } From d2e31b59198cf7d923717e275ebfd3cf1376c33d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 02:37:58 +0900 Subject: [PATCH 311/685] =?UTF-8?q?feat:=20=EB=94=94=EC=8A=A4=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=97=90=EB=9F=AC=20=EB=A1=9C=EA=B9=85=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + nowait-app-admin-api/build.gradle | 8 ++++ .../src/main/resources/logback-admin-dev.xml | 38 +++++++++++++++++++ .../resources/logback-variables.properties | 3 ++ nowait-app-user-api/build.gradle | 7 ++++ .../src/main/resources/logback-user-dev.xml | 38 +++++++++++++++++++ .../resources/logback-variables.properties | 3 ++ 7 files changed, 98 insertions(+) create mode 100644 nowait-app-admin-api/src/main/resources/logback-admin-dev.xml create mode 100644 nowait-app-admin-api/src/main/resources/logback-variables.properties create mode 100644 nowait-app-user-api/src/main/resources/logback-user-dev.xml create mode 100644 nowait-app-user-api/src/main/resources/logback-variables.properties diff --git a/build.gradle b/build.gradle index 94b2c6d6..02c381b0 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ version = '0.0.1-SNAPSHOT' repositories { mavenCentral() + maven { url 'https://jitpack.io' } } allprojects { diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 5568bb77..3ae84d08 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -14,6 +14,7 @@ version = '0.0.1-SNAPSHOT' repositories { mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { @@ -48,6 +49,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' + // 로그 + implementation 'org.springframework.boot:spring-boot-starter-aop' + runtimeOnly 'org.fusesource.jansi:jansi:2.4.0' + + // 디스코드 웹훅 + implementation 'com.github.napstr:logback-discord-appender:1.0.0' + // 기타 필요 라이브러리 (예: Lombok) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/nowait-app-admin-api/src/main/resources/logback-admin-dev.xml b/nowait-app-admin-api/src/main/resources/logback-admin-dev.xml new file mode 100644 index 00000000..ab4c82e1 --- /dev/null +++ b/nowait-app-admin-api/src/main/resources/logback-admin-dev.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + ${DISCORD_WEBHOOK_URL} + + %d{HH:mm:ss} [%thread] [%-5level] %logger{36} - %msg%n```%ex{full}``` + + 감자야...에러 났대... + https://gtablestoreimage.s3.ap-northeast-2.amazonaws.com/%EA%B8%B0%ED%83%80+%EC%9D%B4%EB%AF%B8%EC%A7%80/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3.jpeg + false + + + + + + ERROR + + + + + + + + + + diff --git a/nowait-app-admin-api/src/main/resources/logback-variables.properties b/nowait-app-admin-api/src/main/resources/logback-variables.properties new file mode 100644 index 00000000..b2823949 --- /dev/null +++ b/nowait-app-admin-api/src/main/resources/logback-variables.properties @@ -0,0 +1,3 @@ +LOG_PATH=./logs +LOG_FILE_NAME=nowait +LOG_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%thread] [%logger{36}] - %msg%n diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index afce863a..1a9ce197 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -15,6 +15,7 @@ version = '0.0.1-SNAPSHOT' repositories { mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { @@ -52,6 +53,12 @@ dependencies { // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // 로그 + implementation 'org.springframework.boot:spring-boot-starter-aop' + runtimeOnly 'org.fusesource.jansi:jansi:2.4.0' + + // 디스코드 웹훅 + implementation 'com.github.napstr:logback-discord-appender:1.0.0' // 기타 필요 라이브러리 (예: Lombok) compileOnly 'org.projectlombok:lombok' diff --git a/nowait-app-user-api/src/main/resources/logback-user-dev.xml b/nowait-app-user-api/src/main/resources/logback-user-dev.xml new file mode 100644 index 00000000..3c36ae1f --- /dev/null +++ b/nowait-app-user-api/src/main/resources/logback-user-dev.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + ${DISCORD_WEBHOOK_URL} + + %d{HH:mm:ss} [%thread] [%-5level] %logger{36} - %msg%n```%ex{full}``` + + 감자야...에러 났대... + https://gtablestoreimage.s3.ap-northeast-2.amazonaws.com/%EA%B8%B0%ED%83%80+%EC%9D%B4%EB%AF%B8%EC%A7%80/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3.jpeg + false + + + + + + ERROR + + + + + + + + + + diff --git a/nowait-app-user-api/src/main/resources/logback-variables.properties b/nowait-app-user-api/src/main/resources/logback-variables.properties new file mode 100644 index 00000000..b2823949 --- /dev/null +++ b/nowait-app-user-api/src/main/resources/logback-variables.properties @@ -0,0 +1,3 @@ +LOG_PATH=./logs +LOG_FILE_NAME=nowait +LOG_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%thread] [%logger{36}] - %msg%n From 45dd2328d1e98ef564b408b59452f6fa56ea878e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 11:17:18 +0900 Subject: [PATCH 312/685] =?UTF-8?q?refactor:=20=EB=94=94=EC=8A=A4=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=97=90=EB=9F=AC=20=EB=A1=9C=EA=B9=85=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/logback-admin-dev.xml | 38 ------------------- .../resources/logback-variables.properties | 3 -- .../src/main/resources/logback-user-dev.xml | 38 ------------------- .../resources/logback-variables.properties | 3 -- 4 files changed, 82 deletions(-) delete mode 100644 nowait-app-admin-api/src/main/resources/logback-admin-dev.xml delete mode 100644 nowait-app-admin-api/src/main/resources/logback-variables.properties delete mode 100644 nowait-app-user-api/src/main/resources/logback-user-dev.xml delete mode 100644 nowait-app-user-api/src/main/resources/logback-variables.properties diff --git a/nowait-app-admin-api/src/main/resources/logback-admin-dev.xml b/nowait-app-admin-api/src/main/resources/logback-admin-dev.xml deleted file mode 100644 index ab4c82e1..00000000 --- a/nowait-app-admin-api/src/main/resources/logback-admin-dev.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - ${DISCORD_WEBHOOK_URL} - - %d{HH:mm:ss} [%thread] [%-5level] %logger{36} - %msg%n```%ex{full}``` - - 감자야...에러 났대... - https://gtablestoreimage.s3.ap-northeast-2.amazonaws.com/%EA%B8%B0%ED%83%80+%EC%9D%B4%EB%AF%B8%EC%A7%80/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3.jpeg - false - - - - - - ERROR - - - - - - - - - - diff --git a/nowait-app-admin-api/src/main/resources/logback-variables.properties b/nowait-app-admin-api/src/main/resources/logback-variables.properties deleted file mode 100644 index b2823949..00000000 --- a/nowait-app-admin-api/src/main/resources/logback-variables.properties +++ /dev/null @@ -1,3 +0,0 @@ -LOG_PATH=./logs -LOG_FILE_NAME=nowait -LOG_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%thread] [%logger{36}] - %msg%n diff --git a/nowait-app-user-api/src/main/resources/logback-user-dev.xml b/nowait-app-user-api/src/main/resources/logback-user-dev.xml deleted file mode 100644 index 3c36ae1f..00000000 --- a/nowait-app-user-api/src/main/resources/logback-user-dev.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - ${DISCORD_WEBHOOK_URL} - - %d{HH:mm:ss} [%thread] [%-5level] %logger{36} - %msg%n```%ex{full}``` - - 감자야...에러 났대... - https://gtablestoreimage.s3.ap-northeast-2.amazonaws.com/%EA%B8%B0%ED%83%80+%EC%9D%B4%EB%AF%B8%EC%A7%80/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3.jpeg - false - - - - - - ERROR - - - - - - - - - - diff --git a/nowait-app-user-api/src/main/resources/logback-variables.properties b/nowait-app-user-api/src/main/resources/logback-variables.properties deleted file mode 100644 index b2823949..00000000 --- a/nowait-app-user-api/src/main/resources/logback-variables.properties +++ /dev/null @@ -1,3 +0,0 @@ -LOG_PATH=./logs -LOG_FILE_NAME=nowait -LOG_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%thread] [%logger{36}] - %msg%n From c22dabffe65c7008db025119c40afae8e1a6aa94 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 11:18:11 +0900 Subject: [PATCH 313/685] =?UTF-8?q?feat:=20feign=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/build.gradle | 18 ++- .../java/com/nowait/ApiAdminApplication.java | 2 + .../exception/GlobalExceptionHandler.java | 146 +++++++++++------- nowait-app-user-api/build.gradle | 18 ++- .../java/com/nowait/ApiUserApplication.java | 2 + .../exception/GlobalExceptionHandler.java | 129 ++++++++++------ nowait-infra/build.gradle | 19 +++ .../discord/client/DiscordClientAdmin.java | 18 +++ .../discord/client/DiscordClientUser.java | 18 +++ .../config/DiscordFeignConfiguration.java | 14 ++ .../nowait/discord/dto/DiscordMessage.java | 29 ++++ .../discord/service/DiscordAlarmService.java | 75 +++++++++ 12 files changed, 381 insertions(+), 107 deletions(-) create mode 100644 nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java create mode 100644 nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java create mode 100644 nowait-infra/src/main/java/com/nowait/discord/config/DiscordFeignConfiguration.java create mode 100644 nowait-infra/src/main/java/com/nowait/discord/dto/DiscordMessage.java create mode 100644 nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 3ae84d08..9fba398d 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -17,6 +17,16 @@ repositories { maven { url 'https://jitpack.io' } } +ext { + set('springCloudVersion', "2025.0.0") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + dependencies { implementation project(':nowait-common') implementation project(':nowait-infra') @@ -49,13 +59,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' - // 로그 - implementation 'org.springframework.boot:spring-boot-starter-aop' - runtimeOnly 'org.fusesource.jansi:jansi:2.4.0' - // 디스코드 웹훅 implementation 'com.github.napstr:logback-discord-appender:1.0.0' + //feign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation group: 'io.github.openfeign', name: 'feign-gson', version: '11.0' + // 기타 필요 라이브러리 (예: Lombok) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index b538931a..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -1,11 +1,13 @@ package com.nowait; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableScheduling; @EnableJpaAuditing @EnableScheduling +@EnableFeignClients @SpringBootApplication public class ApiAdminApplication { public static void main(String[] args) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index b2cbf47f..c87fe1be 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -1,8 +1,10 @@ package com.nowait.applicationadmin.exception; import static com.nowait.common.exception.ErrorMessage.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.UNAUTHORIZED; -import static org.springframework.http.HttpStatus.*; import java.util.Map; import java.util.stream.Collectors; @@ -15,12 +17,14 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; import org.springframework.web.multipart.MultipartException; import com.nowait.applicationadmin.security.exception.ResourceNotFoundException; import com.nowait.applicationadmin.security.exception.UnauthorizedException; import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; +import com.nowait.discord.service.DiscordAlarmService; import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; @@ -41,189 +45,224 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import io.swagger.v3.oas.annotations.Hidden; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Hidden @RestControllerAdvice +@RequiredArgsConstructor public class GlobalExceptionHandler { - @ResponseStatus(value = BAD_REQUEST) + private final DiscordAlarmService discordAlarmService; + + // Discord 알림 헬퍼 + private void alarm(Exception e, WebRequest request) { + discordAlarmService.sendDiscordAdminAlarm(e, request); + } + + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(BusinessException.class) - public ErrorResponse handleBusinessException(BusinessException e) { + public ErrorResponse handleBusinessException(BusinessException e, WebRequest request) { + alarm(e, request); log.error("handleBusinessException", e); return new ErrorResponse(e.getMessage(), e.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) - public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e, WebRequest request) { + alarm(e, request); log.error("handleMethodArgumentNotValidException", e); Map errors = getErrors(e); return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(HttpMessageNotReadableException.class) - public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e, WebRequest request) { + alarm(e, request); log.error("handleHttpMessageNotReadableException", e); return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(IllegalArgumentException.class) - public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e, WebRequest request) { + alarm(e, request); log.error("handleIllegalArgumentException", e); return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MissingRequestValueException.class) - public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { + public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e, WebRequest request) { + alarm(e, request); log.error("handleMissingRequestValueExceptionException", e); return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = UNAUTHORIZED) + @ResponseStatus(UNAUTHORIZED) @ExceptionHandler(UnauthorizedException.class) - public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { - log.error("handleUnauthorizedExceptionException", e); + public ErrorResponse handleUnauthorizedException(UnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("handleUnauthorizedException", e); return new ErrorResponse(e.getMessage(), e.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ResourceNotFoundException.class) - public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { + public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e, WebRequest request) { + alarm(e, request); log.error("handleResourceNotFoundExceptionException", e); return new ErrorResponse(e.getMessage(), e.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MultipartException.class) - public ErrorResponse handleMultipartException(MultipartException e) { + public ErrorResponse handleMultipartException(MultipartException e, WebRequest request) { + alarm(e, request); log.error("handleMultipartException", e); return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(UserNotFoundException.class) - public ErrorResponse userNotFoundException(UserNotFoundException e) { + public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { + alarm(e, request); log.error("userNotFoundException", e); return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(OrderParameterEmptyException.class) - public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e) { + public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e, WebRequest request) { + alarm(e, request); log.error("orderParameterEmptyException", e); return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(OrderItemsEmptyException.class) - public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e) { + public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { + alarm(e, request); log.error("orderItemsEmptyException", e); return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(DuplicateOrderException.class) - public ErrorResponse duplicateOrderException(DuplicateOrderException e) { + public ErrorResponse duplicateOrderException(DuplicateOrderException e, WebRequest request) { + alarm(e, request); log.error("duplicateOrderException", e); return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(OrderViewUnauthorizedException.class) - public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e) { + public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("orderViewUnauthorizedException", e); return new ErrorResponse(e.getMessage(), ORDER_VIEW_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(OrderNotFoundException.class) - public ErrorResponse orderNotFoundException(OrderNotFoundException e) { + public ErrorResponse orderNotFoundException(OrderNotFoundException e, WebRequest request) { + alarm(e, request); log.error("orderNotFoundException", e); return new ErrorResponse(e.getMessage(), ORDER_NOT_FOUND.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(OrderUpdateUnauthorizedException.class) - public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedException e) { + public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("orderUpdateUnauthorizedException", e); return new ErrorResponse(e.getMessage(), ORDER_UPDATE_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ReservationNotFoundException.class) - public ErrorResponse reservationNotFoundException(ReservationNotFoundException e) { + public ErrorResponse reservationNotFoundException(ReservationNotFoundException e, WebRequest request) { + alarm(e, request); log.error("reservationNotFoundException", e); return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(ReservationViewUnauthorizedException.class) - public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e) { - log.error("reservationViewUnauthorizedException", e); + public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("reservation_viewUnauthorizedException", e); return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(ReservationUpdateUnauthorizedException.class) - public ErrorResponse reservationUpdateUnauthorizedException(ReservationUpdateUnauthorizedException e) { + public ErrorResponse reservationUpdateUnauthorizedException(ReservationUpdateUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("reservationUpdateUnauthorizedException", e); return new ErrorResponse(e.getMessage(), RESERVATION_UPDATE_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuCreationUnauthorizedException.class) - public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e) { + public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("menuCreationUnauthorizedException", e); return new ErrorResponse(e.getMessage(), MENU_CREATION_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuViewUnauthorizedException.class) - public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e) { + public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("menuViewUnauthorizedException", e); return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuUpdateUnauthorizedException.class) - public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedException e) { + public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("menuUpdateUnauthorizedException", e); return new ErrorResponse(e.getMessage(), MENU_UPDATE_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuDeleteUnauthorizedException.class) - public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e) { + public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("menuDeleteUnauthorizedException", e); return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(StoreViewUnauthorizedException.class) - public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e) { + public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("storeViewUnauthorizedException", e); return new ErrorResponse(e.getMessage(), STORE_VIEW_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(StoreUpdateUnauthorizedException.class) - public ErrorResponse storeUpdateUnauthorizedException(StoreUpdateUnauthorizedException e) { + public ErrorResponse storeUpdateUnauthorizedException(StoreUpdateUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("storeUpdateUnauthorizedException", e); return new ErrorResponse(e.getMessage(), STORE_UPDATE_UNAUTHORIZED.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(StoreDeleteUnauthorizedException.class) - public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e) { + public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e, WebRequest request) { + alarm(e, request); log.error("storeDeleteUnauthorizedException", e); return new ErrorResponse(e.getMessage(), STORE_DELETE_UNAUTHORIZED.getCode()); } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() .getAllErrors() @@ -235,5 +274,4 @@ private static Map getErrors(MethodArgumentNotValidException e) (msg1, msg2) -> msg1 + ";" + msg2 )); } - } diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index 1a9ce197..df5fb1ab 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -18,6 +18,16 @@ repositories { maven { url 'https://jitpack.io' } } +ext { + set('springCloudVersion', "2025.0.0") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + dependencies { implementation project(':nowait-common') implementation project(':nowait-infra') // 사용자 관련 도메인 @@ -53,13 +63,13 @@ dependencies { // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' - // 로그 - implementation 'org.springframework.boot:spring-boot-starter-aop' - runtimeOnly 'org.fusesource.jansi:jansi:2.4.0' - // 디스코드 웹훅 implementation 'com.github.napstr:logback-discord-appender:1.0.0' + //feign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation group: 'io.github.openfeign', name: 'feign-gson', version: '11.0' + // 기타 필요 라이브러리 (예: Lombok) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 07db6d34..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -2,9 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing +@EnableFeignClients @SpringBootApplication public class ApiUserApplication { public static void main(String[] args) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 78a405c0..ef46c2a0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -1,8 +1,11 @@ package com.nowait.applicationuser.exception; import static com.nowait.common.exception.ErrorMessage.*; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.UNAUTHORIZED; -import static org.springframework.http.HttpStatus.*; import java.util.Map; import java.util.stream.Collectors; @@ -16,18 +19,21 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; import org.springframework.web.multipart.MultipartException; import com.nowait.applicationuser.security.exception.ResourceNotFoundException; import com.nowait.applicationuser.security.exception.UnauthorizedException; import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; +import com.nowait.discord.service.DiscordAlarmService; import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -35,150 +41,184 @@ import com.nowait.domainuserrdb.bookmark.exception.DuplicateBookmarkException; import io.swagger.v3.oas.annotations.Hidden; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Hidden +@RequiredArgsConstructor @RestControllerAdvice public class GlobalExceptionHandler { - // OAUTH 인증 실패 에러처리 메서드 - @ResponseStatus(value = BAD_REQUEST) + private final DiscordAlarmService discordAlarmService; + + // Discord 알림 헬퍼 + private void alarm(Exception e, WebRequest request) { + discordAlarmService.sendDiscordUserAlarm(e, request); + } + + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(OAuth2AuthenticationException.class) - public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e) { + public ErrorResponse handlerOAuth2AuthenticationException(OAuth2AuthenticationException e, WebRequest request) { + alarm(e, request); log.error("handleOAuth2AuthenticationException", e); - return new ErrorResponse("OAuth 인증 실패 : " + e.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(BusinessException.class) - public ErrorResponse handleBusinessException(BusinessException e) { + public ErrorResponse handleBusinessException(BusinessException e, WebRequest request) { + alarm(e, request); log.error("handleBusinessException", e); return new ErrorResponse(e.getMessage(), e.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) - public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e, WebRequest request) { + alarm(e, request); log.error("handleMethodArgumentNotValidException", e); Map errors = getErrors(e); return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode(), errors); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(HttpMessageNotReadableException.class) - public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e, WebRequest request) { + alarm(e, request); log.error("handleHttpMessageNotReadableException", e); return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(IllegalArgumentException.class) - public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) { + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e, WebRequest request) { + alarm(e, request); log.error("handleIllegalArgumentException", e); return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MissingRequestValueException.class) - public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e) { - log.error("handleMissingRequestValueExceptionException", e); + public ErrorResponse handleMissingRequestValueException(MissingRequestValueException e, WebRequest request) { + alarm(e, request); + log.error("handleMissingRequestValueException", e); return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = UNAUTHORIZED) + @ResponseStatus(UNAUTHORIZED) @ExceptionHandler(UnauthorizedException.class) - public ErrorResponse handleUnauthorizedException(UnauthorizedException e) { - log.error("handleUnauthorizedExceptionException", e); + public ErrorResponse handleUnauthorizedException(UnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("handleUnauthorizedException", e); return new ErrorResponse(e.getMessage(), e.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ResourceNotFoundException.class) - public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) { - log.error("handleResourceNotFoundExceptionException", e); + public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("handleResourceNotFoundException", e); return new ErrorResponse(e.getMessage(), e.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MultipartException.class) - public ErrorResponse handleMultipartException(MultipartException e) { + public ErrorResponse handleMultipartException(MultipartException e, WebRequest request) { + alarm(e, request); log.error("handleMultipartException", e); return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(DuplicateBookmarkException.class) - public ErrorResponse handleDuplicateBookmarkException(DuplicateBookmarkException e) { + public ErrorResponse handleDuplicateBookmarkException(DuplicateBookmarkException e, WebRequest request) { + alarm(e, request); log.error("handleDuplicateBookmarkException", e); return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_BOOKMARK.getCode()); } - @ResponseStatus(value = FORBIDDEN) + @ResponseStatus(FORBIDDEN) @ExceptionHandler(BookmarkOwnerMismatchException.class) - public ErrorResponse bookmarkOwnerMismatchException(BookmarkOwnerMismatchException e) { + public ErrorResponse bookmarkOwnerMismatchException(BookmarkOwnerMismatchException e, WebRequest request) { + alarm(e, request); log.error("bookmarkOwnerMismatchException", e); return new ErrorResponse(e.getMessage(), NOT_OWN_BOOKMARK.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(UserNotFoundException.class) - public ErrorResponse userNotFoundException(UserNotFoundException e) { + public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { + alarm(e, request); log.error("userNotFoundException", e); return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(OrderParameterEmptyException.class) - public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e) { + public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e, WebRequest request) { + alarm(e, request); log.error("orderParameterEmptyException", e); return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(OrderItemsEmptyException.class) - public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e) { + public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { + alarm(e, request); log.error("orderItemsEmptyException", e); return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(DepositorNameTooLongException.class) - public ErrorResponse depositorNameTooLongException(DepositorNameTooLongException e) { + public ErrorResponse depositorNameTooLongException(DepositorNameTooLongException e, WebRequest request) { + alarm(e, request); log.error("depositorNameTooLongException", e); return new ErrorResponse(e.getMessage(), DEPOSITOR_NAME_TOO_LONG.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(DuplicateOrderException.class) - public ErrorResponse duplicateOrderException(DuplicateOrderException e) { + public ErrorResponse duplicateOrderException(DuplicateOrderException e, WebRequest request) { + alarm(e, request); log.error("duplicateOrderException", e); return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); } - @ResponseStatus(value = NOT_FOUND) + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ReservationNotFoundException.class) - public ErrorResponse reservationNotFoundException(ReservationNotFoundException e) { + public ErrorResponse reservationNotFoundException(ReservationNotFoundException e, WebRequest request) { + alarm(e, request); log.error("reservationNotFoundException", e); return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(DuplicateReservationException.class) - public ErrorResponse duplicateReservationException(DuplicateReservationException e) { + public ErrorResponse duplicateReservationException(DuplicateReservationException e, WebRequest request) { + alarm(e, request); log.error("duplicateReservationException", e); return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); } - @ResponseStatus(value = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(StoreWaitingDisabledException.class) - public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException e) { + public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException e, WebRequest request) { + alarm(e, request); log.error("storeWaitingDisabledException", e); return new ErrorResponse(e.getMessage(), STORE_WAITING_DISABLED.getCode()); } + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StoreNotFoundException.class) + public ErrorResponse handleStoreNotFoundException(StoreNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("handleStoreNotFoundException", e); + return new ErrorResponse(e.getMessage(), STORE_NOT_FOUND.getCode()); + } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() .getAllErrors() @@ -190,5 +230,4 @@ private static Map getErrors(MethodArgumentNotValidException e) (msg1, msg2) -> msg1 + ";" + msg2 )); } - } diff --git a/nowait-infra/build.gradle b/nowait-infra/build.gradle index 5cbc1f17..ce38b13d 100644 --- a/nowait-infra/build.gradle +++ b/nowait-infra/build.gradle @@ -20,17 +20,36 @@ repositories { mavenCentral() } +ext { + set('springCloudVersion', "2025.0.0") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + + dependencies { // Multipart File Upload // 추후 분리 필요 implementation 'org.springframework.boot:spring-boot-starter-web' + // 비동기 실행 implementation 'org.springframework.boot:spring-boot-starter-aop' + // S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + // Resilience4j implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' implementation 'io.github.resilience4j:resilience4j-bulkhead:2.3.0' + + //feign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation group: 'io.github.openfeign', name: 'feign-gson', version: '11.0' + // lombok compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' diff --git a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java new file mode 100644 index 00000000..7d9adabd --- /dev/null +++ b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java @@ -0,0 +1,18 @@ +package com.nowait.discord.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import com.nowait.discord.config.DiscordFeignConfiguration; +import com.nowait.discord.dto.DiscordMessage; + +@FeignClient( + name = "discordClientAdmin", + contextId = "discordClientAdmin", + url = "https://discord.com/api/webhooks/1399070629994565653/Cy1_421v3-rN7U_7mKO7tcbwnGxS6Ufmf-T-uorAp8Bn0EoqDlrnKKvaf91PftxD1fRi", + configuration = DiscordFeignConfiguration.class) +public interface DiscordClientAdmin { + @PostMapping() + void sendAlarm(@RequestBody DiscordMessage message); +} diff --git a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java new file mode 100644 index 00000000..a295e740 --- /dev/null +++ b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java @@ -0,0 +1,18 @@ +package com.nowait.discord.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import com.nowait.discord.config.DiscordFeignConfiguration; +import com.nowait.discord.dto.DiscordMessage; + +@FeignClient( + name = "discordClientUser", + contextId = "discordClientUser", + url = "https://discord.com/api/webhooks/1399081071127171203/dvop__zPnQKCX-1VMJyBYnU_KTFNJFUwgGwta9D1Zy0xON0hKDcTW2ke9TQfnywzD2ll", + configuration = DiscordFeignConfiguration.class) +public interface DiscordClientUser { + @PostMapping() + void sendAlarm(@RequestBody DiscordMessage message); +} diff --git a/nowait-infra/src/main/java/com/nowait/discord/config/DiscordFeignConfiguration.java b/nowait-infra/src/main/java/com/nowait/discord/config/DiscordFeignConfiguration.java new file mode 100644 index 00000000..2c16cf7a --- /dev/null +++ b/nowait-infra/src/main/java/com/nowait/discord/config/DiscordFeignConfiguration.java @@ -0,0 +1,14 @@ +package com.nowait.discord.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import feign.Logger; + +@Configuration +public class DiscordFeignConfiguration { + @Bean + Logger.Level feignLoggerLevel(){ + return Logger.Level.FULL; + } +} diff --git a/nowait-infra/src/main/java/com/nowait/discord/dto/DiscordMessage.java b/nowait-infra/src/main/java/com/nowait/discord/dto/DiscordMessage.java new file mode 100644 index 00000000..8d3bd13c --- /dev/null +++ b/nowait-infra/src/main/java/com/nowait/discord/dto/DiscordMessage.java @@ -0,0 +1,29 @@ +package com.nowait.discord.dto; + +import java.util.List; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class DiscordMessage { + + private String content; + private List embeds; + + @Builder + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + @Getter + public static class Embed { + + private String title; + private String description; + } +} diff --git a/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java b/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java new file mode 100644 index 00000000..8eaa0e2e --- /dev/null +++ b/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java @@ -0,0 +1,75 @@ +package com.nowait.discord.service; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; + +import com.nowait.discord.client.DiscordClientAdmin; +import com.nowait.discord.dto.DiscordMessage; +import com.nowait.discord.client.DiscordClientUser; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +@RequiredArgsConstructor +public class DiscordAlarmService { + + private final DiscordClientUser discordClientUser; + private final DiscordClientAdmin discordClientAdmin; + + public void sendDiscordUserAlarm(Exception e, WebRequest request) { + discordClientUser.sendAlarm(createMessage(e, request)); + } + + public void sendDiscordAdminAlarm(Exception e, WebRequest request) { + discordClientAdmin.sendAlarm(createMessage(e, request)); + } + + private DiscordMessage createMessage(Exception e, WebRequest request) { + return DiscordMessage.builder() + .content("# 🚨 에러 발생 비이이이이사아아아앙") + .embeds( + List.of( + DiscordMessage.Embed.builder() + .title("ℹ️ 에러 정보") + .description( + "### 🕖 발생 시간\n" + + LocalDateTime.now() + + "\n" + + "### 🔗 요청 URL\n" + + createRequestFullPath(request) + + "\n" + + "### 📄 Stack Trace\n" + + "```\n" + + getStackTrace(e).substring(0, 1000) + + "\n```") + .build() + ) + ) + .build(); + } + + private String createRequestFullPath(WebRequest webRequest) { + HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest(); + String fullPath = request.getMethod() + " " + request.getRequestURL(); + String queryString = request.getQueryString(); + if (queryString != null) { + fullPath += "?" + queryString; + } + return fullPath; + } + + private String getStackTrace(Exception e) { + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } +} From c31771dfed631e0aa5231ec64987a8ee8bfad91b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 11:28:30 +0900 Subject: [PATCH 314/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=EB=A7=8C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B0=80=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discord/service/DiscordAlarmService.java | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java b/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java index 8eaa0e2e..3e4e1425 100644 --- a/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java +++ b/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java @@ -3,8 +3,10 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; @@ -24,41 +26,42 @@ public class DiscordAlarmService { private final DiscordClientUser discordClientUser; private final DiscordClientAdmin discordClientAdmin; + private final Environment environment; public void sendDiscordUserAlarm(Exception e, WebRequest request) { - discordClientUser.sendAlarm(createMessage(e, request)); + if (shouldNotifyUser()) { + discordClientUser.sendAlarm(createMessage(e, request)); + } } public void sendDiscordAdminAlarm(Exception e, WebRequest request) { - discordClientAdmin.sendAlarm(createMessage(e, request)); + if (shouldNotifyAdmin()) { + discordClientAdmin.sendAlarm(createMessage(e, request)); + } + } + + private boolean shouldNotifyAdmin() { + return Arrays.stream(environment.getActiveProfiles()).noneMatch("admin-local"::equals); + } + + private boolean shouldNotifyUser() { + return Arrays.stream(environment.getActiveProfiles()).noneMatch("user-local"::equals); } private DiscordMessage createMessage(Exception e, WebRequest request) { return DiscordMessage.builder() .content("# 🚨 에러 발생 비이이이이사아아아앙") - .embeds( - List.of( - DiscordMessage.Embed.builder() - .title("ℹ️ 에러 정보") - .description( - "### 🕖 발생 시간\n" - + LocalDateTime.now() - + "\n" - + "### 🔗 요청 URL\n" - + createRequestFullPath(request) - + "\n" - + "### 📄 Stack Trace\n" - + "```\n" - + getStackTrace(e).substring(0, 1000) - + "\n```") - .build() - ) - ) + .embeds(List.of(DiscordMessage.Embed.builder() + .title("ℹ️ 에러 정보") + .description( + "### 🕖 발생 시간\n" + LocalDateTime.now() + "\n" + "### 🔗 요청 URL\n" + createRequestFullPath(request) + + "\n" + "### 📄 Stack Trace\n" + "```\n" + getStackTrace(e).substring(0, 1000) + "\n```") + .build())) .build(); } private String createRequestFullPath(WebRequest webRequest) { - HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest(); + HttpServletRequest request = ((ServletWebRequest)webRequest).getRequest(); String fullPath = request.getMethod() + " " + request.getRequestURL(); String queryString = request.getQueryString(); if (queryString != null) { From 28d6949566f5725a743f6c48d9fa9e891178b324 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:30:32 +0900 Subject: [PATCH 315/685] =?UTF-8?q?refactor(Reservation):=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?,=20=ED=99=95=EC=A0=95,=20=EC=B7=A8=EC=86=8C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=81=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/EntryStatusResponseDto.java | 47 ++++++++++++++++++- .../reservation/entity/Reservation.java | 27 +++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java index 907b924b..4322e197 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java @@ -2,6 +2,9 @@ import java.time.LocalDateTime; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.reservation.entity.Reservation; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -11,7 +14,10 @@ @Builder public class EntryStatusResponseDto { @Schema(description = "예약 ID", example = "1201") - private String id; // reservationId + private String reservationId; // reservationId + + @Schema(description = "예약 번호", example = "23-240504-0001") + private String reservationNumber; @Schema(description = "유저 ID", example = "16") private String userId; @@ -25,6 +31,15 @@ public class EntryStatusResponseDto { @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") private LocalDateTime createdAt; + @Schema(description = "호출 시각", example = "2025-07-22T16:00:00") + private LocalDateTime calledAt; // 호출 시각 + + @Schema(description = "입장 완료 처리 시각", example = "2025-07-22T16:00:00") + private LocalDateTime confirmedAt; // 완료 시각 + + @Schema(description = "웨이팅 취소 시각", example = "2025-07-22T16:00:00") + private LocalDateTime cancelledAt; // 취소 시각 + @Schema(description = "대기 상태", example = "CALLING") private String status; @@ -33,5 +48,35 @@ public class EntryStatusResponseDto { @Schema(description = "호출 메시지", example = "호출 메시지") private String message; + + public static EntryStatusResponseDto fromEntity(Reservation r) { + return EntryStatusResponseDto.builder() + .reservationId(r.getId().toString()) + .reservationNumber(r.getReservationNumber()) + .userId(r.getUser().getId().toString()) + .partySize(r.getPartySize()) + .userName(r.getUser().getNickname()) + .createdAt(r.getRequestedAt()) + .status(r.getStatus().name()) + .calledAt(r.getCalledAt()) + .confirmedAt(r.getConfirmedAt()) + .cancelledAt(r.getCancelledAt()) + .message(switch (r.getStatus()) { + case CALLING -> r.getUser().getNickname() + "님을 호출하였습니다."; + case CONFIRMED -> r.getUser().getNickname() + "님의 입장이 완료되었습니다."; + case CANCELLED -> r.getUser().getNickname() + "님의 예약이 취소되었습니다."; + default -> ""; + }) + .build(); + } + + private String buildMessage(ReservationStatus status, String nickname) { + return switch (status) { + case CALLING -> nickname + "님을 호출하였습니다."; + case CONFIRMED -> nickname + "님의 입장이 완료되었습니다."; + case CANCELLED -> nickname + "님의 예약이 취소되었습니다."; + default -> ""; + }; + } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index 32742420..6857a7a8 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -35,6 +35,9 @@ public class Reservation { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "reservation_number", nullable = false, length = 50) + private String reservationNumber; + @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "store_id") private Store store; @@ -46,6 +49,15 @@ public class Reservation { @Column(name = "requested_at", nullable = false) private LocalDateTime requestedAt; + @Column(name = "called_at", nullable = true) + private LocalDateTime calledAt; // 호출 시각 + + @Column(name = "confirmed_at", nullable = true) + private LocalDateTime confirmedAt; // 확정(입장 완료) 시각 + + @Column(name = "cancelled_at", nullable = true) + private LocalDateTime cancelledAt; // 취소 시각 + @Enumerated(EnumType.STRING) @Column(nullable = false) private ReservationStatus status; @@ -57,4 +69,19 @@ public void updateStatus(ReservationStatus status) { this.status = status; } + // 상태 전환 메서드 + public void markCalling(LocalDateTime ts) { + this.status = ReservationStatus.CALLING; + this.calledAt = ts; + } + + public void markConfirmed(LocalDateTime ts) { + this.status = ReservationStatus.CONFIRMED; + this.confirmedAt = ts; + } + + public void markCancelled(LocalDateTime ts) { + this.status = ReservationStatus.CANCELLED; + this.cancelledAt = ts; + } } From 7a6f9581d73b9f9a32926c87ca8108b73a0d42fa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:30:50 +0900 Subject: [PATCH 316/685] =?UTF-8?q?refactor(Reservation):=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20Id=20=EB=B0=8F=20=EB=B0=B0=EB=84=88=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/reservation/dto/MyWaitingQueueDto.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java index 2190698b..0314d398 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java @@ -1,6 +1,7 @@ package com.nowait.applicationuser.reservation.dto; import java.time.LocalDateTime; +import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -14,6 +15,8 @@ @AllArgsConstructor @Schema(description = "내 대기 큐 정보 DTO") public class MyWaitingQueueDto { + @Schema(description = "예약 ID", example = "1-20240720-0001") + private String reservationId; @Schema(description = "주점 ID", example = "1") private Long storeId; @Schema(description = "주점 이름", example = "비어파티") @@ -34,5 +37,7 @@ public class MyWaitingQueueDto { private String location; @Schema(description = "프로필 이미지 URL", example = "https://cdn.gtable.com/profile/user1.jpg") private String profileImageUrl; + @Schema(description = "배너 이미지 URL", example = "https://cdn.gtable.com/profile/user1.jpg") + private List bannerImageUrl; } From 22750124e2d471e01fdbe5af18be01aed8781f45 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:30:59 +0900 Subject: [PATCH 317/685] =?UTF-8?q?refactor(Reservation):=20MyWaitingStore?= =?UTF-8?q?Info=20Dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/MyWaitingStoreInfo.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java new file mode 100644 index 00000000..eea3d273 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.reservation.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class MyWaitingStoreInfo { + private final Long storeId; + private final String storeName; + private final String departmentName; + private final String location; +} From 34e20872b7685065caeb4c45a9988afae784d61a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:31:31 +0900 Subject: [PATCH 318/685] =?UTF-8?q?refactor(Reservation):=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=95=BD=EB=B2=88=ED=98=B8=20=EB=B0=8F=20?= =?UTF-8?q?calling=20=ED=82=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/util/RedisKeyUtils.java | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index af6a9ed1..ca37768d 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -1,6 +1,10 @@ package com.nowait.domaincoreredis.common.util; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Date; public class RedisKeyUtils { @@ -18,7 +22,6 @@ public class RedisKeyUtils { private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:"; private static final String WAITING_STATUS_KEY_PREFIX = "waiting:status:"; - private RedisKeyUtils() { throw new UnsupportedOperationException("유틸리티 서비스는 인스턴스화 할 수 없습니다."); } @@ -35,11 +38,48 @@ public static String buildNextKey() { return KEY_NEXT; } - public static String buildMenuKey() { return KEY_FMT; } + public static String buildMenuKey() { + return KEY_FMT; + } - public static DateTimeFormatter buildMenuDateKey() { return DTF; } + public static DateTimeFormatter buildMenuDateKey() { + return DTF; + } + + public static String buildWaitingKeyPrefix() { + return WAITING_KEY_PREFIX; + } + + public static String buildWaitingPartySizeKeyPrefix() { + return WAITING_PARTYSIZE_KEY_PREFIX; + } + + public static String buildWaitingStatusKeyPrefix() { + return WAITING_STATUS_KEY_PREFIX; + } - public static String buildWaitingKeyPrefix() { return WAITING_KEY_PREFIX; } - public static String buildWaitingPartySizeKeyPrefix() { return WAITING_PARTYSIZE_KEY_PREFIX; } - public static String buildWaitingStatusKeyPrefix() { return WAITING_STATUS_KEY_PREFIX; } + // Waiting Reservation Number key + public static String buildReservationSeqKey(Long storeId) { + return String.format("reservation:seq:%d", storeId); + } + + public static String buildReservationNumberKey(Long storeId) { + return String.format("reservation:number:%d", storeId); + } + + /** + * 대기 호출 시각(hash)에 사용할 키 접두사 + */ + public static String buildWaitingCalledAtKeyPrefix() { + return "waiting:calledAt:"; + } + + public static Date expireAtNext03() { + ZoneId zone = ZoneId.of("Asia/Seoul"); + LocalDateTime now = LocalDateTime.now(zone); + LocalDateTime next03 = now.toLocalDate().plusDays(1).atTime(3, 0); + Instant instant = next03.atZone(zone).toInstant(); + + return Date.from(instant); + } } From 514d13b6e4e6433ab9b6fddcf41bdfd998245d15 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:31:55 +0900 Subject: [PATCH 319/685] =?UTF-8?q?refactor(Reservation):=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index 5133d2b9..6ec51e54 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -8,18 +8,15 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusRequest; import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.applicationadmin.reservation.service.ReservationService; import com.nowait.common.api.ApiUtils; -import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; @@ -40,7 +37,13 @@ public class ReservationController { @ApiResponse(responseCode = "200", description = "주점별 전체 대기 리스트 조회") public ResponseEntity getWaitingUsersWithScore(@PathVariable Long storeId) { List response = reservationService.getAllWaitingUserDetails(storeId); - return ResponseEntity.ok(response); + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); } @GetMapping("/admin/{storeId}/completed") @@ -51,7 +54,13 @@ public ResponseEntity getCompletedReservationList( @AuthenticationPrincipal MemberDetails memberDetails ) { List response = reservationService.getCompletedWaitingUserDetails(storeId); - return ResponseEntity.ok(response); + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); } // @PatchMapping("/admin/{storeId}/call/{userId}") From 256b71519c164fad361309a0af209c23d46e92a3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:32:04 +0900 Subject: [PATCH 320/685] =?UTF-8?q?refactor(Reservation):=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 10434638..6c1dfe99 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -57,7 +57,7 @@ public ResponseEntity createQueue( @AuthenticationPrincipal CustomOAuth2User customOAuth2User, @RequestBody ReservationCreateRequestDto requestDto ) { - WaitingResponseDto response = reservationService.registerWaiting(storeId,customOAuth2User,requestDto); + WaitingResponseDto response = reservationService.registerWaiting(storeId, customOAuth2User, requestDto); return ResponseEntity .status(HttpStatus.CREATED) .body( @@ -74,7 +74,7 @@ public ResponseEntity getQueue( @PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User ) { - WaitingResponseDto response = reservationService.myWaitingInfo(storeId,customOAuth2User); + WaitingResponseDto response = reservationService.myWaitingInfo(storeId, customOAuth2User); return ResponseEntity .ok() .body( @@ -95,7 +95,7 @@ public ResponseEntity deleteQueue( .ok() .body( ApiUtils.success( - reservationService.cancelWaiting(storeId,customOAuth2User) + reservationService.cancelWaiting(storeId, customOAuth2User) ) ); } @@ -105,7 +105,13 @@ public ResponseEntity deleteQueue( @ApiResponse(responseCode = "200", description = "대기열 리스트 조회") public ResponseEntity getAllMyWaitings(@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { List response = reservationService.getAllMyWaitings(customOAuth2User); - return ResponseEntity.ok(ApiUtils.success(response)); + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); } } From 17100291a1fe05be3e6de1adc9bbfd0f71e9174f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:32:16 +0900 Subject: [PATCH 321/685] =?UTF-8?q?refactor(Reservation):=20statuses=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/ReservationRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index f3e74b38..61efa124 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -22,8 +22,8 @@ public interface ReservationRepository extends JpaRepository Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween( Long storeId, Long userId, LocalDateTime start, LocalDateTime end); - Optional findFirstByStore_StoreIdAndUserIdAndRequestedAtBetweenOrderByRequestedAtDesc( - Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + Optional findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( + Long storeId, Long userId, List statuses, LocalDateTime start, LocalDateTime end); List findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( Long storeId, List statuses, LocalDateTime start, LocalDateTime end); From dd57d701d9161eb2df268744bccd4d8356198113 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:33:11 +0900 Subject: [PATCH 322/685] =?UTF-8?q?refactor(Reservation):=20ThreadLocalRan?= =?UTF-8?q?dom=EC=9C=BC=EB=A1=9C=20ID=20=EB=A7=8C=EB=93=9C=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81.=20N=20+=201=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 258 +++++++++++------- 1 file changed, 164 insertions(+), 94 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index b8dfd999..c95a6248 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -1,20 +1,25 @@ package com.nowait.applicationadmin.reservation.service; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; -import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; @@ -28,12 +33,12 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; -import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import lombok.RequiredArgsConstructor; @@ -45,6 +50,7 @@ public class ReservationService { private final UserRepository userRepository; private final WaitingRedisRepository waitingRedisRepository; private final StoreRepository storeRepository; + private final RedisTemplate redisTemplate; //TODO 성능 비교를 위해 남겨둔 로직 @Transactional(readOnly = true) @@ -98,50 +104,74 @@ public CallGetResponseDto updateReservationStatus(Long reservationId, Reservatio } // Redis queue에 있는 주점별 전체 대기열 조회 + //TODO 개선필요 -> createAt 정확성 및 reservationhId 생성 방법(예약생성부터 DB에 박아야하나....) @Transactional(readOnly = true) public List getAllWaitingUserDetails(Long storeId) { + + // 1) Redis로부터 전체 대기 userId + score(등록 시각) List> waitingList = waitingRedisRepository.getAllWaitingWithScore(storeId); System.out.println(waitingList); + if (waitingList.isEmpty()) { + return List.of(); + } - // TODO N + 1 발생 -> 개선 필요 - return waitingList.stream() - .map(tuple -> { - String userId = tuple.getValue(); - - // 1. Redis에서 partySize/status 조회 - Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); - String status = waitingRedisRepository.getWaitingStatus(storeId, userId); - - // 2. DB에서 userName, createdAt, reservationId 조회 - //TODO 개선필요 -> createAt 정확성 및 reservationhId 생성 방법(예약생성부터 DB에 박아야하나....) - String userName = userRepository.getReferenceById(Long.valueOf(userId)).getNickname(); - LocalDateTime createdAt = LocalDateTime.now(); - String reservationId = String.valueOf( - ThreadLocalRandom.current().nextInt(1, 100)); - - Optional reservationOpt = reservationRepository.findFirstByStore_StoreIdAndUserIdAndRequestedAtBetweenOrderByRequestedAtDesc( - storeId, Long.valueOf(userId), LocalDate.now().atStartOfDay(), - LocalDate.now().atTime(LocalTime.MAX)); - if (reservationOpt.isPresent()) { - Reservation reservation = reservationOpt.get(); - createdAt = reservation.getRequestedAt(); - reservationId = reservation.getId().toString(); - userName = reservation.getUser().getNickname(); - } else { - } + // 2) userId 목록 분리 + List userIds = waitingList.stream() + .map(ZSetOperations.TypedTuple::getValue) + .toList(); - return new WaitingUserResponse( - reservationId != null ? reservationId.toString() : null, - userId, - partySize, - userName, - createdAt, - status, - tuple.getScore() - ); - }) + // 3) User 닉네임을 한 번에 배치 조회 + List userIdLongs = userIds.stream() + .map(Long::valueOf) .toList(); + Map nicknameMap = userRepository.findAllById(userIdLongs).stream() + .collect(Collectors.toMap( + u -> u.getId().toString(), + User::getNickname + )); + + // 4) Redis 파이프라인: partySize, status, reservationId + String pk = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String sk = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + String nk = RedisKeyUtils.buildReservationNumberKey(storeId); + String qk = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + + List pipeline = redisTemplate.executePipelined((RedisCallback)conn -> { + byte[] uid; + for (String userId : userIds) { + uid = redisTemplate.getStringSerializer().serialize(userId); + conn.hGet(pk.getBytes(), uid); // partySize + conn.hGet(sk.getBytes(), uid); // status + conn.hGet(nk.getBytes(), uid); // reservationId + conn.zScore(qk.getBytes(), uid); // score (등록 시각) + } + return null; + }); + + // 5) 결과 매핑 + List result = new ArrayList<>(userIds.size()); + Iterator it = pipeline.iterator(); + ZoneId zone = ZoneId.of("Asia/Seoul"); + + for (ZSetOperations.TypedTuple tuple : waitingList) { + String userId = tuple.getValue(); + Integer partySize = Optional.ofNullable((String)it.next()).map(Integer::valueOf).orElse(0); + String status = (String)it.next(); + String reservationId = (String)it.next(); + Double score = (Double)it.next(); + + // score → createdAt + LocalDateTime createdAt = score != null + ? Instant.ofEpochMilli(score.longValue()).atZone(zone).toLocalDateTime() + : null; + + String userName = nicknameMap.getOrDefault(userId, "Unknown"); + + result.add( + WaitingUserResponse.fromRedis(reservationId, userId, partySize, userName, createdAt, status, score)); + } + return result; } // 완료 or 취소 처리된 대기 리스트 조회 @@ -190,77 +220,117 @@ private Reservation findTodayReservation(Long storeId, String userId) { * - CANCELLED : Redis에서 삭제 → DB 저장 → 취소 메시지 반환 */ @Transactional - public EntryStatusResponseDto processEntryStatus( - Long storeId, - String userId, - MemberDetails member, - ReservationStatus newStatus - ) { - User manager = authorize(storeId, member); - User user = userRepository.findById(Long.valueOf(userId)).orElseThrow(UserNotFoundException::new); - - String message = null; - Reservation reservation; + public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, ReservationStatus newStatus) { + + authorize(storeId, member); + + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + + // Redis에서 상태·score·partySize·calledAt 조회 + String reservationNumber = waitingRedisRepository.getReservationId(storeId, userId); + String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId); + Double score = redisTemplate.opsForZSet().score(queueKey, userId); + Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + Long calledMillis = waitingRedisRepository.getWaitingCalledAt(storeId, userId); + + LocalDateTime requestedAt = score != null + ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() + : LocalDateTime.now(); + LocalDateTime calledAt = calledMillis != null + ? Instant.ofEpochMilli(calledMillis).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() + : null; + LocalDateTime now = LocalDateTime.now(); switch (newStatus) { case CALLING: - // 1) Redis 상태 검사 & 변경 - String curr = waitingRedisRepository.getWaitingStatus(storeId, userId); - if (!ReservationStatus.WAITING.name().equals(curr)) { - throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); + if (!ReservationStatus.WAITING.name().equals(currStatus)) { + throw new IllegalStateException("WAITING 상태에서만 CALLING 가능합니다."); } waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); + waitingRedisRepository.setWaitingCalledAt(storeId, userId, now.toInstant(ZoneOffset.ofHours(9)).toEpochMilli()); - // 2) 파티 인원, 호출 시각 - Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); - LocalDateTime now = LocalDateTime.now(); - // 3) DB에 무조건 새로 저장 - Store store = storeRepository.getReferenceById(storeId); - reservation = Reservation.builder() - .store(store) - .user(user) + return EntryStatusResponseDto.builder() + .reservationNumber(reservationNumber) + .userId(userId) .partySize(partySize) - .requestedAt(now) - .status(ReservationStatus.CALLING) + .userName(userRepository.getReferenceById(Long.valueOf(userId)).getNickname()) + .createdAt(requestedAt) + .status("CALLING") + .score(score) + .calledAt(now) + .message("호출되었습니다.") .build(); - reservationRepository.save(reservation); - - break; case CONFIRMED: + // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 + if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus)) { + + // Redis 전부 삭제 + waitingRedisRepository.deleteWaiting(storeId, userId); + + // 새 Reservation 생성 & 저장 + Reservation r = Reservation.builder() + .reservationNumber(reservationNumber) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.valueOf(userId))) + .partySize(partySize) + .requestedAt(requestedAt) + .build(); + // 호출 시각 반영 + r.markCalling(calledAt != null ? calledAt : now); + if (newStatus == ReservationStatus.CONFIRMED) { + r.markConfirmed(now); + } else { + r.markCancelled(now); + } + + Reservation saved = reservationRepository.save(r); + return EntryStatusResponseDto.fromEntity(saved); + } + + // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 + // (Redis에는 상태가 남아있지 않으므로 currStatus==null) + { + LocalDateTime start = LocalDate.now().atStartOfDay(); + LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); + Reservation existing = reservationRepository + .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( + storeId, + Long.valueOf(userId), + List.of(ReservationStatus.CANCELLED), + start, + end + ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); + + existing.markConfirmed(now); + existing.updateStatus(ReservationStatus.CONFIRMED); + Reservation saved = reservationRepository.save(existing); + return EntryStatusResponseDto.fromEntity(saved); + } + case CANCELLED: - // 1) Redis에서 제거 + if (!(ReservationStatus.WAITING.name().equals(currStatus) + || ReservationStatus.CALLING.name().equals(currStatus))) { + throw new IllegalStateException("WAITING/CALLING 상태에서만 취소 가능합니다."); + } waitingRedisRepository.deleteWaiting(storeId, userId); - // 2) 오늘 날짜 예약 조회 & 상태 변경 - reservation = findTodayReservation(storeId, userId); - reservation.updateStatus(newStatus); - reservationRepository.save(reservation); - - // 3) 완료/취소 메시지 - message = String.format( - "%s님의 예약이 %s 처리되었습니다.", - user.getNickname(), - newStatus == ReservationStatus.CONFIRMED ? "입장 완료" : "입장 취소" - ); - break; + Reservation r = Reservation.builder() + .reservationNumber(reservationNumber) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.valueOf(userId))) + .partySize(partySize) + .requestedAt(requestedAt) + .build(); + r.markCalling(calledAt != null ? calledAt : now); + r.markCancelled(now); + Reservation saved = reservationRepository.save(r); + return EntryStatusResponseDto.fromEntity(saved); default: - throw new IllegalArgumentException("지원하지 않는 상태입니다: " + newStatus); + throw new IllegalArgumentException("지원하지 않는 상태: " + newStatus); } - - // 5) 공통 DTO 반환 - return EntryStatusResponseDto.builder() - .id(reservation.getId().toString()) - .userId(userId) - .partySize(reservation.getPartySize()) - .userName(user.getNickname()) - .createdAt(reservation.getRequestedAt()) - .status(reservation.getStatus().name()) - .message(message) - .build(); } - } From 1a72411befe547b5d1b9f0bee85815e20223a45a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:33:19 +0900 Subject: [PATCH 323/685] =?UTF-8?q?refactor(Reservation):=20ThreadLocalRan?= =?UTF-8?q?dom=EC=9C=BC=EB=A1=9C=20ID=20=EB=A7=8C=EB=93=9C=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81.=20N=20+=201=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 155 +++++++++++++----- 1 file changed, 110 insertions(+), 45 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 959ac887..04fc4eab 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -4,16 +4,21 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto; +import com.nowait.applicationuser.reservation.dto.MyWaitingStoreInfo; import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; @@ -25,13 +30,17 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.entity.StoreImage; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; +import com.nowait.domaincorerdb.store.repository.StoreImageRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import lombok.RequiredArgsConstructor; @@ -45,15 +54,18 @@ public class ReservationService { private final UserRepository userRepository; private final WaitingUserRedisRepository waitingUserRedisRepository; private final DepartmentRepository departmentRepository; + private final StoreImageRepository storeImageRepository; + private final RedisTemplate redisTemplate; public WaitingResponseDto registerWaiting( - Long storeId,CustomOAuth2User customOAuth2User,ReservationCreateRequestDto requestDto + Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto ) { // Store 유효성 검증 추가 Store store = storeRepository.findById(storeId) .orElseThrow(StoreNotFoundException::new); if (Boolean.FALSE.equals(store.getIsActive())) throw new StoreWaitingDisabledException(); + // User Role 검증 추가 User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); @@ -65,13 +77,16 @@ public WaitingResponseDto registerWaiting( long timestamp = System.currentTimeMillis(); // 예약 신청 유저 큐(queue)에 추가 - boolean added = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), timestamp); - if (!added) { - throw new IllegalArgumentException("Failed to add to waiting queue"); + String reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), + timestamp); + if (reservationId == null) { + throw new IllegalStateException("예약 번호 발급 실패"); } + // 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환 Long rank = waitingUserRedisRepository.getRank(storeId, userId); return WaitingResponseDto.builder() + .reservationNumber(reservationId) .rank(rank == null ? -1 : rank.intValue() + 1) .partySize(requestDto.getPartySize() == null ? 0 : requestDto.getPartySize()) .build(); @@ -81,11 +96,13 @@ public WaitingResponseDto myWaitingInfo(Long storeId, CustomOAuth2User customOAu String userId = customOAuth2User.getUserId().toString(); // 입력 검증 추가 if (storeId == null || userId.trim().isEmpty()) { - throw new IllegalArgumentException("Invalid storeId or userId"); + throw new IllegalArgumentException("Invalid storeId or userId"); } Long rank = waitingUserRedisRepository.getRank(storeId, userId); Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); + String reservationId = waitingUserRedisRepository.getReservationId(storeId, userId); return WaitingResponseDto.builder() + .reservationNumber(reservationId) .rank(rank == null ? -1 : rank.intValue() + 1) .partySize(partySize == null ? 0 : partySize) .build(); @@ -103,54 +120,102 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { } return removed; } + //TODO 성능 개선 필요 public List getAllMyWaitings(CustomOAuth2User customOAuth2User) { String userId = customOAuth2User.getUserId().toString(); - List userWaitingStoreIds = waitingUserRedisRepository.getUserWaitingStoreIds(userId); - - List result = new ArrayList<>(); - if (!userWaitingStoreIds.isEmpty()) { - // Store, Department 배치 조회 - List stores = storeRepository.findAllById(userWaitingStoreIds); - Map storeMap = stores.stream() - .collect(Collectors.toMap(Store::getStoreId, Function.identity())); - - Set departmentIds = stores.stream() - .map(Store::getDepartmentId) - .collect(Collectors.toSet()); - Map departmentNameMap = departmentRepository.findAllById(departmentIds).stream() - .collect(Collectors.toMap(Department::getId, Department::getName)); - - for (Long storeId : userWaitingStoreIds) { - Store store = storeMap.get(storeId); - if (store == null) continue; - - Long rank = waitingUserRedisRepository.getRank(storeId, userId); - Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); - Long timestamp = waitingUserRedisRepository.getWaitingTimestamp(storeId, userId); - - LocalDateTime registeredAt = timestamp != null - ? LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Seoul")) - : null; - - result.add(MyWaitingQueueDto.builder() - .storeId(storeId) - .storeName(store.getName()) - .departmentName(departmentNameMap.get(store.getDepartmentId())) - .rank(rank != null ? rank.intValue() + 1 : 0) - .teamsAhead(rank != null ? rank.intValue() : 0) - .partySize(partySize != null ? partySize : 0) - .status(waitingUserRedisRepository.getWaitingStatus(storeId, userId)) // 필요시 redis에 상태값이 있으면 조회해서 세팅 - .registeredAt(registeredAt) - .location(store.getLocation()) - .profileImageUrl(customOAuth2User.getUser().getProfileImage()) - .build()); + + // 1) 현재 SCAN 기반으로 얻어온 storeId 리스트 + List storeIds = waitingUserRedisRepository.getUserWaitingStoreIds(userId); + if (storeIds.isEmpty()) + return Collections.emptyList(); + + // 2) Store, Department 배치 조회 + List stores = storeRepository.findAllWithDepartmentByStoreIdIn(storeIds); + Map storeMap = stores.stream() + .collect(Collectors.toMap(Store::getStoreId, Function.identity())); + + // 3) Department 이름 조회 (departmentId 기준) + Set deptIds = stores.stream() + .map(Store::getDepartmentId) + .collect(Collectors.toSet()); + Map deptNameMap = departmentRepository.findAllById(deptIds).stream() + .collect(Collectors.toMap(Department::getId, Department::getName)); + + // 4) StoreImage 조회 (프로필 + 배너) + List neededTypes = List.of(ImageType.PROFILE, ImageType.BANNER); + List images = storeImageRepository.findAllByStore_StoreIdInAndImageTypeIn(storeIds, neededTypes); + + Map profileMap = images.stream() + .filter(img -> img.getImageType() == ImageType.PROFILE) + .collect(Collectors.toMap( + img -> img.getStore().getStoreId(), + StoreImage::getImageUrl, + (first, second) -> first + )); + Map> bannerMap = images.stream() + .filter(img -> img.getImageType() == ImageType.BANNER) + .collect(Collectors.groupingBy( + img -> img.getStore().getStoreId(), + Collectors.mapping(StoreImage::getImageUrl, Collectors.toList()) + )); + + List pipelineResults = redisTemplate.executePipelined((RedisCallback)conn -> { + byte[] uid = redisTemplate.getStringSerializer().serialize(userId); + for (Long storeId : storeIds) { + String qk = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String pk = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String sk = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + String nk = RedisKeyUtils.buildReservationNumberKey(storeId); + conn.zRank(qk.getBytes(), uid); // 순번 (0-based) + conn.hGet(pk.getBytes(), uid); // partySize (String) + conn.zScore(qk.getBytes(), uid); // timestamp (Double) + conn.hGet(sk.getBytes(), uid); // status (String) + conn.hGet(nk.getBytes(), uid); // reservationId (String) } + return null; + }); + + // 4) 결과 매핑 + List result = new ArrayList<>(storeIds.size()); + Iterator it = pipelineResults.iterator(); + for (Long storeId : storeIds) { + Store store = storeMap.get(storeId); + Long rankObj = (Long)it.next(); // null 가능 + String partyStr = (String)it.next(); + Double tsScore = (Double)it.next(); + String status = (String)it.next(); + String reservationId = (String)it.next(); + + int rank = (rankObj != null ? rankObj.intValue() + 1 : 0); + int teamsAhead = (rankObj != null ? rankObj.intValue() : 0); + int partySize = (partyStr != null ? Integer.parseInt(partyStr) : 0); + LocalDateTime registeredAt = tsScore != null + ? Instant.ofEpochMilli(tsScore.longValue()) + .atZone(ZoneId.of("Asia/Seoul")) + .toLocalDateTime() + : null; + + result.add(MyWaitingQueueDto.builder() + .reservationId(reservationId) + .storeId(storeId) + .storeName(store.getName()) + .departmentName(deptNameMap.get(store.getDepartmentId())) + .location(store.getLocation()) + .profileImageUrl(profileMap.getOrDefault(storeId, "")) + .bannerImageUrl(bannerMap.getOrDefault(storeId, Collections.emptyList())) + .rank(rank) + .teamsAhead(teamsAhead) + .partySize(partySize) + .status(status) + .registeredAt(registeredAt) + .build() + ); } + return result; } - @Transactional public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto) { From a11281f9285fb252fd8165a09663035605963203 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:33:38 +0900 Subject: [PATCH 324/685] =?UTF-8?q?refactor(Reservation):=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20findAllByStore=5FStoreIdInAndImageTypeIn=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/store/repository/StoreImageRepository.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java index d327fdb0..1b495a7e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java @@ -1,7 +1,5 @@ package com.nowait.domaincorerdb.store.repository; -import static com.nowait.domaincorerdb.store.entity.ImageType.*; - import java.util.List; import java.util.Optional; @@ -23,5 +21,6 @@ public interface StoreImageRepository extends JpaRepository { List findByStoreAndImageType(Store store, ImageType imageType); + List findAllByStore_StoreIdInAndImageTypeIn(List storeIds, List imageTypes); } From bb5f427c20e0d2cbc47f2138682117b8b61217c0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:33:53 +0900 Subject: [PATCH 325/685] =?UTF-8?q?refactor(Reservation):=20findAllWithDep?= =?UTF-8?q?artmentByStoreIdIn=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/store/repository/StoreRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index b5fe61d0..05e689fc 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -36,4 +36,13 @@ OR MATCH(d.name) AGAINST(:kw) List searchByKeywordNative(@Param("kw") String booleanKeyword); List findAllByStoreIdInOrderByStoreIdAsc(List storeIds); + + @Query(value = """ + SELECT s + FROM Store s + LEFT JOIN Department d ON s.departmentId = d.id + WHERE s.deleted = false + AND s.storeId IN :ids + """) + List findAllWithDepartmentByStoreIdIn(@Param("ids") List ids); } From 1d04861f0ceb173256c56896eca227aecf033194 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:34:30 +0900 Subject: [PATCH 326/685] =?UTF-8?q?refactor(Reservation):=20ReservationNum?= =?UTF-8?q?ber=20=EB=B0=8F=20setWaitingCalledAt=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingRedisRepository.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java index f8610e65..e21233a2 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java @@ -1,7 +1,7 @@ package com.nowait.applicationadmin.reservation.repository; -import java.time.Duration; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Set; @@ -17,6 +17,7 @@ @RequiredArgsConstructor public class WaitingRedisRepository { private final StringRedisTemplate redisTemplate; + private final Date expireAt = RedisKeyUtils.expireAtNext03(); // 대기열 전체 인원수 조회 public List> getAllWaitingWithScore(Long storeId) { @@ -54,6 +55,19 @@ public Integer getWaitingPartySize(Long storeId, String userId) { return value == null ? null : Integer.valueOf(value.toString()); } + // ReservationNumber 조회 + public String getReservationId(Long storeId, String userId) { + String status = getWaitingStatus(storeId, userId); + if (!"WAITING".equals(status) && !"CALLING".equals(status)) { + // 이미 종료된 대기라면, 예약 번호도 없던 것처럼 취급 + return null; + } + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + Object val = redisTemplate.opsForHash().get(numberMapKey, userId); + + return val != null ? val.toString() : null; + } + public void deleteWaiting(Long storeId, String userId) { String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForHash().delete(statusKey, userId); @@ -63,6 +77,26 @@ public void deleteWaiting(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; redisTemplate.opsForHash().delete(partyKey, userId); + + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + redisTemplate.opsForHash().delete(numberMapKey, userId); + + String key = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; + redisTemplate.opsForHash().delete(key, userId); + } + + // 호출 시각 기록 + public void setWaitingCalledAt(Long storeId, String userId, long timestamp) { + String key = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; + redisTemplate.opsForHash().put(key, userId, String.valueOf(timestamp)); + + redisTemplate.expireAt(key, expireAt); + } + + public Long getWaitingCalledAt(Long storeId, String userId) { + String key = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; + Object val = redisTemplate.opsForHash().get(key, userId); + return val == null ? null : Long.valueOf(val.toString()); } } From a1a950cdcd379aa144c7dff394e6d309e69ce1eb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:34:40 +0900 Subject: [PATCH 327/685] =?UTF-8?q?refactor(Reservation):=20ReservationNum?= =?UTF-8?q?ber=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/reservation/dto/WaitingResponseDto.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java index 36527069..631c7771 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java @@ -6,6 +6,7 @@ @Getter @Builder public class WaitingResponseDto { + private final String reservationNumber; private final int rank; private final boolean reserved; // true면 예약, false면 대기 private final int partySize; From 492e687850f7ab56b95e7597142501380f1e6414 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:34:49 +0900 Subject: [PATCH 328/685] =?UTF-8?q?refactor(Reservation):=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WaitingUserRedisRepository.java | 142 +++++++++++++++--- 1 file changed, 123 insertions(+), 19 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 16c84901..08fb899b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -1,10 +1,22 @@ package com.nowait.applicationuser.reservation.repository; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; @@ -19,21 +31,50 @@ public class WaitingUserRedisRepository { // 중복 등록 방지: 이미 있으면 추가X // 특정 주점에 대한 예약 등록 - public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, long timestamp) { + public String addToWaitingQueue(Long storeId, String userId, Integer partySize, long timestamp) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + String seqKey = RedisKeyUtils.buildReservationSeqKey(storeId); + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); + String reservationId; + if (Boolean.TRUE.equals(added)) { + + // 2) 일일 시퀀스: 날짜별로 초기화하려면 + String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // YYYYMMDD + String dailySeqKey = seqKey + ":" + today; // ex. reservation:seq:5:20250728 + + // atomic increment + Long seq = redisTemplate.opsForValue().increment(dailySeqKey, 1); + + // 3) 4자리 0패딩 + String seqStr = String.format("%04d", seq); + + // 4) 최종 ID 조합 + reservationId = storeId + "-" + today + "-" + seqStr; + + // 5) Hash에 저장 + redisTemplate.opsForHash().put(numberMapKey, userId, reservationId); + + // 6) 기존 partySize, status, TTL 설정 redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); - // TTL 12시간(43200초) 설정 - redisTemplate.expire(queueKey, Duration.ofHours(12)); - redisTemplate.expire(partyKey, Duration.ofHours(12)); - redisTemplate.expire(statusKey, Duration.ofHours(12)); + + Duration ttl = setTTL(partyKey, statusKey, userId, partySize); + + redisTemplate.expire(queueKey, ttl); + redisTemplate.expire(partyKey, ttl); + redisTemplate.expire(statusKey, ttl); + } else { + Object stored = redisTemplate.opsForHash().get(numberMapKey, userId); + reservationId = stored != null ? stored.toString() : null; } - return Boolean.TRUE.equals(added); + + return reservationId; } // 예약한 사람이 등록한 동반인원(partySize) 조회 @@ -54,9 +95,11 @@ public boolean removeWaiting(Long storeId, String userId) { String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + String reservationNumberKey = RedisKeyUtils.buildReservationNumberKey(storeId); redisTemplate.opsForZSet().remove(key, userId); redisTemplate.opsForHash().delete(partyKey, userId); redisTemplate.opsForHash().delete(statusKey, userId); + redisTemplate.opsForHash().delete(reservationNumberKey, userId); return true; } @@ -69,21 +112,59 @@ public Long getWaitingTimestamp(Long storeId, String userId) { // 사용자가 대기중인 전체 매장 목록 조회 public List getUserWaitingStoreIds(String userId) { - // key pattern으로 모든 매장 대기열 조회 (keys: waiting:*) - Set keys = redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*"); - if (keys == null) - return List.of(); + String prefix = RedisKeyUtils.buildWaitingKeyPrefix(); + String pattern = prefix + "*"; + + // 1) SCAN 으로 모든 키 수집 + Set matchingKeys = new HashSet<>(); + ScanOptions options = ScanOptions.scanOptions() + .match(pattern) + .count(500) // 한 번에 스캔할 예상 키 개수 힌트 (조정 가능) + .build(); + + // 2) Cursor 로 비차단 스캔 + try (Cursor cursor = + redisTemplate.getConnectionFactory() + .getConnection() + .scan(options)) { + while (cursor.hasNext()) { + String key = new String(cursor.next(), StandardCharsets.UTF_8); + matchingKeys.add(key); + } + } - List result = new ArrayList<>(); - for (String key : keys) { - // ZSet만 필터링 - String type = redisTemplate.type(key).code(); - if (!"zset".equals(type)) { - continue; // hash 등은 스킵! + if (matchingKeys.isEmpty()) { + return Collections.emptyList(); + } + + // 2) ZSet 타입 키만 필터링 + List zsetKeys = matchingKeys.stream() + .filter(key -> "zset".equals(redisTemplate.type(key).code())) + .toList(); + + if (zsetKeys.isEmpty()) { + return Collections.emptyList(); + } + + // 3) 파이프라인으로 zRank 한 번에 조회 + List pipelineResults = redisTemplate.executePipelined( + (RedisCallback) conn -> { + byte[] uid = redisTemplate.getStringSerializer().serialize(userId); + for (String key : zsetKeys) { + byte[] rawKey = redisTemplate.getStringSerializer().serialize(key); + conn.zRank(rawKey, uid); + } + return null; } - // waiting:{storeId}만 추출 (waiting:party:7 등은 통과 안 됨) - Long storeId = Long.valueOf(key.substring(key.lastIndexOf(":") + 1)); - if (redisTemplate.opsForZSet().rank(key, userId) != null) { + ); + + // 4) zsetKeys 로 루프를 돌며 결과 매핑 + List result = new ArrayList<>(); + Iterator it = pipelineResults.iterator(); + for (String key : zsetKeys) { + Object rankObj = it.next(); // pipelineResults 개수와 정확히 매칭 + if (rankObj != null) { + long storeId = Long.parseLong(key.substring(prefix.length())); result.add(storeId); } } @@ -101,6 +182,29 @@ public String getWaitingStatus(Long storeId, String userId) { public boolean isUserWaiting(Long storeId, String userId) { return getRank(storeId, userId) != null; } + + // ReservationNumber 조회 + public String getReservationId(Long storeId, String userId) { + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + Object val = redisTemplate.opsForHash().get(numberMapKey, userId); + + return val != null ? val.toString() : null; + } + + public Duration setTTL(String partyKey, String statusKey, String userId, Integer partySize) { + // 6) 기존 partySize, status, TTL 설정 + redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); + redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); + + // 6-1) Asia/Seoul 기준으로 오늘 자정(내일 00:00) 구하기 + ZoneId zone = ZoneId.of("Asia/Seoul"); + LocalDateTime now = LocalDateTime.now(zone); + LocalDateTime midnight = now.toLocalDate().plusDays(1).atTime(3, 0); + // 6-2) TTL 남은 초 계산 + long secondsUntilMidnight = now.until(midnight, ChronoUnit.SECONDS); + + return Duration.ofSeconds(secondsUntilMidnight); + } } From d509b605bad50361b6fd3abb4cb1639bf02d4037 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:35:07 +0900 Subject: [PATCH 329/685] =?UTF-8?q?refactor(Reservation):=20reservationId?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20fromRedis=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index 71c1c490..881b68ec 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -16,8 +16,8 @@ @Schema(description = "대기 사용자 응답 DTO") public class WaitingUserResponse { - @Schema(description = "예약 ID", example = "1201") - private String id; // reservationId + @Schema(description = "예약 ID", example = "16-20240201-0002") + private String reservationId; @Schema(description = "유저 ID", example = "16") private String userId; @@ -39,7 +39,7 @@ public class WaitingUserResponse { public static WaitingUserResponse fromEntity(Reservation reservation) { return WaitingUserResponse.builder() - .id(reservation.getId().toString()) + .reservationId(reservation.getId().toString()) .userId(reservation.getUser().getId().toString()) .partySize(reservation.getPartySize()) .userName(reservation.getUser().getNickname()) @@ -47,4 +47,16 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { .status(reservation.getStatus().name()) .build(); } + + public static WaitingUserResponse fromRedis(String reservationId, String userId, Integer partySize, String userName, LocalDateTime createdAt, String status, Double score) { + return WaitingUserResponse.builder() + .reservationId(reservationId) + .userId(userId) + .partySize(partySize) + .userName(userName) + .createdAt(createdAt) + .status(status) + .score(score) + .build(); + } } From e3290e3da46dc8629adb476594840f4a54fd4026 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:47:22 +0900 Subject: [PATCH 330/685] =?UTF-8?q?refactor(Reservation):=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/EntryStatusResponseDto.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java index 4322e197..1b6ad2cc 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java @@ -69,14 +69,5 @@ public static EntryStatusResponseDto fromEntity(Reservation r) { }) .build(); } - - private String buildMessage(ReservationStatus status, String nickname) { - return switch (status) { - case CALLING -> nickname + "님을 호출하였습니다."; - case CONFIRMED -> nickname + "님의 입장이 완료되었습니다."; - case CANCELLED -> nickname + "님의 예약이 취소되었습니다."; - default -> ""; - }; - } } From 53e0ca29be0d3d35d2152d9b25b26c2a325eac67 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:49:33 +0900 Subject: [PATCH 331/685] =?UTF-8?q?refactor(Reservation):=20=EB=A7=A4?= =?UTF-8?q?=ED=95=91=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index 881b68ec..95fbf33a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -17,7 +17,7 @@ public class WaitingUserResponse { @Schema(description = "예약 ID", example = "16-20240201-0002") - private String reservationId; + private String reservationNumber; @Schema(description = "유저 ID", example = "16") private String userId; @@ -39,7 +39,7 @@ public class WaitingUserResponse { public static WaitingUserResponse fromEntity(Reservation reservation) { return WaitingUserResponse.builder() - .reservationId(reservation.getId().toString()) + .reservationNumber(reservation.getReservationNumber()) .userId(reservation.getUser().getId().toString()) .partySize(reservation.getPartySize()) .userName(reservation.getUser().getNickname()) @@ -50,7 +50,7 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { public static WaitingUserResponse fromRedis(String reservationId, String userId, Integer partySize, String userName, LocalDateTime createdAt, String status, Double score) { return WaitingUserResponse.builder() - .reservationId(reservationId) + .reservationNumber(reservationId) .userId(userId) .partySize(partySize) .userName(userName) From 3971be7ffb2e99a7d1c931203d491376b2c83d12 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:50:27 +0900 Subject: [PATCH 332/685] =?UTF-8?q?refactor(Reservation):=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=ED=98=B8=EC=B6=9C=20=EC=8B=9C?= =?UTF-8?q?=EC=A0=90=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingRedisRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java index e21233a2..02e29c82 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java @@ -16,8 +16,8 @@ @Repository @RequiredArgsConstructor public class WaitingRedisRepository { + private final StringRedisTemplate redisTemplate; - private final Date expireAt = RedisKeyUtils.expireAtNext03(); // 대기열 전체 인원수 조회 public List> getAllWaitingWithScore(Long storeId) { @@ -90,7 +90,7 @@ public void setWaitingCalledAt(Long storeId, String userId, long timestamp) { String key = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; redisTemplate.opsForHash().put(key, userId, String.valueOf(timestamp)); - redisTemplate.expireAt(key, expireAt); + redisTemplate.expireAt(key, RedisKeyUtils.expireAtNext03()); } public Long getWaitingCalledAt(Long storeId, String userId) { From 325cec2226adeca84aaba275a87209c0616826a6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:56:43 +0900 Subject: [PATCH 333/685] =?UTF-8?q?refactor(Reservation):=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=B2=98=EB=A6=AC=20=EC=9D=BC=EA=B4=80=EC=84=B1?= =?UTF-8?q?=EC=9E=88=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index c95a6248..6ba78a76 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -247,7 +247,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me throw new IllegalStateException("WAITING 상태에서만 CALLING 가능합니다."); } waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); - waitingRedisRepository.setWaitingCalledAt(storeId, userId, now.toInstant(ZoneOffset.ofHours(9)).toEpochMilli()); + waitingRedisRepository.setWaitingCalledAt(storeId, userId, now.atZone(ZoneId.of("Asia/Seoul")).toInstant().toEpochMilli()); return EntryStatusResponseDto.builder() From c5aabd3afed6c2f67ec3590006a63c39ed07c663 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 19:58:35 +0900 Subject: [PATCH 334/685] =?UTF-8?q?refactor(Reservation):=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=AC=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 6ba78a76..bbb163da 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -287,28 +287,26 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me Reservation saved = reservationRepository.save(r); return EntryStatusResponseDto.fromEntity(saved); + } else { + // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 + // TODO 메서드로 분리 + LocalDateTime start = LocalDate.now().atStartOfDay(); + LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); + Reservation existing = reservationRepository + .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( + storeId, + Long.valueOf(userId), + List.of(ReservationStatus.CANCELLED), + start, + end + ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); + + existing.markConfirmed(now); + existing.updateStatus(ReservationStatus.CONFIRMED); + Reservation saved = reservationRepository.save(existing); + return EntryStatusResponseDto.fromEntity(saved); } - // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 - // (Redis에는 상태가 남아있지 않으므로 currStatus==null) - { - LocalDateTime start = LocalDate.now().atStartOfDay(); - LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); - Reservation existing = reservationRepository - .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( - storeId, - Long.valueOf(userId), - List.of(ReservationStatus.CANCELLED), - start, - end - ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); - - existing.markConfirmed(now); - existing.updateStatus(ReservationStatus.CONFIRMED); - Reservation saved = reservationRepository.save(existing); - return EntryStatusResponseDto.fromEntity(saved); - } - case CANCELLED: if (!(ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus))) { From f56ac5c0a94d47c935d17bf7dfef7569f0fc6114 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 20:00:05 +0900 Subject: [PATCH 335/685] =?UTF-8?q?refactor(Reservation):=20SRP=20?= =?UTF-8?q?=EC=9C=84=EB=B0=98=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingUserRedisRepository.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 08fb899b..ccf73412 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -36,8 +36,8 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; - String seqKey = RedisKeyUtils.buildReservationSeqKey(storeId); - String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + String seqKey = RedisKeyUtils.buildReservationSeqKey(storeId); + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); String reservationId; @@ -64,7 +64,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); - Duration ttl = setTTL(partyKey, statusKey, userId, partySize); + Duration ttl = calculateTTLUntilNext03AM(); redisTemplate.expire(queueKey, ttl); redisTemplate.expire(partyKey, ttl); @@ -148,7 +148,7 @@ public List getUserWaitingStoreIds(String userId) { // 3) 파이프라인으로 zRank 한 번에 조회 List pipelineResults = redisTemplate.executePipelined( - (RedisCallback) conn -> { + (RedisCallback)conn -> { byte[] uid = redisTemplate.getStringSerializer().serialize(userId); for (String key : zsetKeys) { byte[] rawKey = redisTemplate.getStringSerializer().serialize(key); @@ -191,15 +191,12 @@ public String getReservationId(Long storeId, String userId) { return val != null ? val.toString() : null; } - public Duration setTTL(String partyKey, String statusKey, String userId, Integer partySize) { - // 6) 기존 partySize, status, TTL 설정 - redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); - redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); - + public Duration calculateTTLUntilNext03AM() { // 6-1) Asia/Seoul 기준으로 오늘 자정(내일 00:00) 구하기 ZoneId zone = ZoneId.of("Asia/Seoul"); - LocalDateTime now = LocalDateTime.now(zone); + LocalDateTime now = LocalDateTime.now(zone); LocalDateTime midnight = now.toLocalDate().plusDays(1).atTime(3, 0); + // 6-2) TTL 남은 초 계산 long secondsUntilMidnight = now.until(midnight, ChronoUnit.SECONDS); From 57ece0f3e65b2b796722c2da796969466e3a7e5e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 28 Jul 2025 20:01:11 +0900 Subject: [PATCH 336/685] =?UTF-8?q?refactor(Reservation):=20not=20null=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/domaincorerdb/reservation/entity/Reservation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index 6857a7a8..2f64fb34 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -35,7 +35,7 @@ public class Reservation { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "reservation_number", nullable = false, length = 50) + @Column(name = "reservation_number", nullable = true, length = 50) private String reservationNumber; @ManyToOne(fetch = FetchType.LAZY, optional = false) From 7eeb91a02a8cf95fee660cfe8c996f7974cd599e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 12:07:39 +0900 Subject: [PATCH 337/685] =?UTF-8?q?refactor(Reservation):=20bookmarkedSet?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/bookmark/service/BookmarkService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 63436967..e5fe86c8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -1,7 +1,9 @@ package com.nowait.applicationuser.bookmark.service; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -60,7 +62,9 @@ public List getBookmarks(CustomOAuth2User customOAuth2User) { .map(Store::getStoreId) .toList(); - return storeService.getAllStoresByPageAndDeparments(storeIds); + Set bookmarkedSet = new HashSet<>(storeIds); + + return storeService.getAllStoresByPageAndDeparments(storeIds, bookmarkedSet); } @Transactional From 264fd3d5d28832ec4de67ad6e367b2a37cacfdc0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 12:09:33 +0900 Subject: [PATCH 338/685] =?UTF-8?q?refactor(Reservation):=20StorePageReadD?= =?UTF-8?q?to=EB=A5=BC=20StorePageReadResponse=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreDepartmentReadResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java index fe04584f..e052524b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java @@ -11,12 +11,12 @@ @Builder public class StoreDepartmentReadResponse { - private List storePageReadDtos; + private List storePageReadDtos; private boolean hasNext; - public static StoreDepartmentReadResponse of(List storePageReadDtos, boolean hasNext) { + public static StoreDepartmentReadResponse of(List storePageReadResponses, boolean hasNext) { return StoreDepartmentReadResponse.builder() - .storePageReadDtos(storePageReadDtos) + .storePageReadDtos(storePageReadResponses) .hasNext(hasNext) .build(); } From dcea2a4ab72f0d17dd8adb72d5453f8e9333510f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 12:10:37 +0900 Subject: [PATCH 339/685] =?UTF-8?q?refactor(Reservation):=20fromEntity?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreDetailReadResponse.java | 43 +++---------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java index 3ccf1674..c40550e7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java @@ -15,6 +15,7 @@ @Builder public class StoreDetailReadResponse { private Long storeId; + private Boolean isBookmark; private Long waitingCount; private Boolean isWaiting; private Long departmentId; @@ -29,9 +30,10 @@ public class StoreDetailReadResponse { private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; - private Boolean isBookmarked; - public static StoreDetailReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isWaiting) { + public static StoreDetailReadResponse fromEntity( + Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmark, Boolean isWaiting + ) { StoreImageUploadResponse profile = allImages.stream() .filter(image -> image.getImageType() == ImageType.PROFILE) @@ -43,8 +45,8 @@ public static StoreDetailReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmarked, Boolean isWaiting - ) { - - StoreImageUploadResponse profile = allImages.stream() - .filter(image -> image.getImageType() == ImageType.PROFILE) - .findFirst() - .orElse(null); - - List banners = allImages.stream() - .filter(image -> image.getImageType() == ImageType.BANNER) - .toList(); - - return StoreDetailReadResponse.builder() - .createdAt(store.getCreatedAt()) - .storeId(store.getStoreId()) - .waitingCount(waitingCount) - .isWaiting(isWaiting) - .departmentId(store.getDepartmentId()) - .departmentName(departmentName) - .name(store.getName()) - .location(store.getLocation()) - .description(store.getDescription()) - .notice(store.getNotice()) - .openTime(store.getOpenTime()) .isActive(store.getIsActive()) .deleted(store.getDeleted()) - .profileImage(profile) - .bannerImages(banners) - .isBookmarked(isBookmarked) + .createdAt(store.getCreatedAt()) .build(); } } From 6c6c0a494e5f7050a0c20ae7106498e589bfa9a7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 12:10:43 +0900 Subject: [PATCH 340/685] =?UTF-8?q?refactor(Reservation):=20fromEntity?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StorePageReadDto.java | 40 ++----------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java index bae989be..05b3b280 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadDto.java @@ -15,6 +15,7 @@ @Builder public class StorePageReadDto { private Long storeId; + private Boolean isBookmark; private Long waitingCount; private Long departmentId; private String departmentName; @@ -28,9 +29,8 @@ public class StorePageReadDto { private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; - private Boolean isBookmarked; - public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount) { + public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmark) { StoreImageUploadResponse profile = allImages.stream() .filter(image -> image.getImageType() == ImageType.PROFILE) @@ -42,8 +42,8 @@ public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmarked - ) { - - StoreImageUploadResponse profile = allImages.stream() - .filter(image -> image.getImageType() == ImageType.PROFILE) - .findFirst() - .orElse(null); - - List banners = allImages.stream() - .filter(image -> image.getImageType() == ImageType.BANNER) - .toList(); - - return StorePageReadDto.builder() - .createdAt(store.getCreatedAt()) - .storeId(store.getStoreId()) - .waitingCount(waitingCount) - .departmentId(store.getDepartmentId()) - .departmentName(departmentName) - .name(store.getName()) - .location(store.getLocation()) - .description(store.getDescription()) - .notice(store.getNotice()) - .openTime(store.getOpenTime()) .isActive(store.getIsActive()) .deleted(store.getDeleted()) - .profileImage(profile) - .bannerImages(banners) - .isBookmarked(isBookmarked) + .createdAt(store.getCreatedAt()) .build(); } } From 42743741ea05ff81ad61263f9f3d25d2ffd6a960 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 12:10:54 +0900 Subject: [PATCH 341/685] =?UTF-8?q?refactor(Reservation):=20StorePageReadR?= =?UTF-8?q?esponse=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StorePageReadResponse.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java new file mode 100644 index 00000000..6066f75e --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java @@ -0,0 +1,60 @@ +package com.nowait.applicationuser.store.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.nowait.domaincorerdb.store.entity.ImageType; +import com.nowait.domaincorerdb.store.entity.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StorePageReadResponse { + private Long storeId; + private Long waitingCount; + private Long departmentId; + private String departmentName; + private String name; + private String location; + private String description; + private String notice; + private String openTime; + private StoreImageUploadResponse profileImage; + private List bannerImages; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + + public static StorePageReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + + return StorePageReadResponse.builder() + .storeId(store.getStoreId()) + .waitingCount(waitingCount) + .departmentId(store.getDepartmentId()) + .departmentName(departmentName) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .notice(store.getNotice()) + .openTime(store.getOpenTime()) + .profileImage(profile) + .bannerImages(banners) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .createdAt(store.getCreatedAt()) + .build(); + } +} From 0340240a0e7d22a38677f924df3b00ded49a3549 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 12:11:47 +0900 Subject: [PATCH 342/685] =?UTF-8?q?refactor(Reservation):=20getAllStoresBy?= =?UTF-8?q?PageAndDeparments=20=EB=82=B4=20Set=20bookmarkedSet=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreService.java | 8 +++--- .../store/service/StoreServiceImpl.java | 27 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index d7b450d7..7900d032 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -1,14 +1,14 @@ package com.nowait.applicationuser.store.service; import java.util.List; +import java.util.Set; import org.springframework.data.domain.Pageable; import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; import com.nowait.applicationuser.store.dto.StoreDetailReadResponse; import com.nowait.applicationuser.store.dto.StorePageReadDto; -import com.nowait.applicationuser.store.dto.StoreReadDto; -import com.nowait.applicationuser.store.dto.StoreReadResponse; +import com.nowait.applicationuser.store.dto.StorePageReadResponse; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -18,9 +18,9 @@ public interface StoreService { StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User); - List searchByKeywordNative(String name); + List searchByKeywordNative(String name); List getStoresByWaitingCount(boolean desc); - List getAllStoresByPageAndDeparments(List storeIds); + List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 408c18d3..4743feb2 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -5,6 +5,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -16,7 +17,6 @@ import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +25,7 @@ import com.nowait.applicationuser.store.dto.StoreDetailReadResponse; import com.nowait.applicationuser.store.dto.StoreImageUploadResponse; import com.nowait.applicationuser.store.dto.StorePageReadDto; +import com.nowait.applicationuser.store.dto.StorePageReadResponse; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; @@ -105,7 +106,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page )); // 5) Dto 매핑 - List content = stores.stream() + List content = stores.stream() .map(store -> { List imgs = imageMap .getOrDefault(store.getStoreId(), List.of()); @@ -113,7 +114,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page .getOrDefault(store.getDepartmentId(), "Unknown Department"); Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount); + return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount); }) .toList(); @@ -136,6 +137,8 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User .map(Department::getName) .orElse("Unknown Department"); + boolean isBookmark = bookmarkRepository.existsByUserAndStore(user, store); + // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 String key = "waiting:" + storeId; long waitingSize = redisTemplate.opsForZSet().zCard(key); @@ -146,22 +149,16 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User .map(StoreImageUploadResponse::fromEntity) .toList(); - if (bookmarkRepository.existsByUserAndStore(user, store)) { - return StoreDetailReadResponse.fromEntityWithBookmark(store, imageDto, departmentName, waitingSize, true, userWaiting); - } else { - return StoreDetailReadResponse.fromEntity(store, imageDto, departmentName, waitingSize, userWaiting); - } + return StoreDetailReadResponse.fromEntity(store, imageDto, departmentName, waitingSize, isBookmark, userWaiting); } @Override - public List searchByKeywordNative(String keyword) { + public List searchByKeywordNative(String keyword) { if (keyword == null || keyword.isBlank()) { throw new StoreParamEmptyException(); } // 1) 페이징된 Store 스냅샷 조회 - // Like 사용 - // List stores = storeRepository.findByNameContainingIgnoreCaseAndDeletedFalse(keyword); // 풀텍스트인덱스 사용 List stores = storeRepository.searchByKeywordNative(keyword); @@ -213,7 +210,7 @@ public List searchByKeywordNative(String keyword) { .getOrDefault(store.getDepartmentId(), "Unknown Department"); Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount); + return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount); }) .toList(); } @@ -288,7 +285,7 @@ private RedisConnection getSafeConnection() { @Override @Transactional(readOnly = true) - public List getAllStoresByPageAndDeparments(List storeIds) { + public List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet) { // 1) 페이징된 Store 스냅샷 조회 List stores = storeRepository.findAllByStoreIdInOrderByStoreIdAsc(storeIds); @@ -339,7 +336,9 @@ public List getAllStoresByPageAndDeparments(List storeId .getOrDefault(store.getDepartmentId(), "Unknown Department"); Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount); + boolean isBookmark = bookmarkedSet.contains(store.getStoreId()); + + return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount, isBookmark); }) .toList(); From 9e2f03edc1505418cd42f2a10487812416bc523a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 15:13:35 +0900 Subject: [PATCH 343/685] =?UTF-8?q?refactor(Bookmark):=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=90=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20isBookmar?= =?UTF-8?q?k=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/service/BookmarkService.java | 4 +- .../store/controller/StoreController.java | 5 +- .../dto/StoreDepartmentReadResponse.java | 4 +- .../store/dto/StorePageReadResponse.java | 4 +- ...eReadDto.java => StoreSearchResponse.java} | 22 ++------- .../store/service/StoreService.java | 9 ++-- .../store/service/StoreServiceImpl.java | 46 +++++++++++++------ .../repository/BookmarkRepository.java | 4 ++ 8 files changed, 54 insertions(+), 44 deletions(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/{StorePageReadDto.java => StoreSearchResponse.java} (58%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index e5fe86c8..744d5201 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -9,7 +9,7 @@ import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.bookmark.dto.BookmarkCreateResponse; -import com.nowait.applicationuser.store.dto.StorePageReadDto; +import com.nowait.applicationuser.store.dto.StorePageReadResponse; import com.nowait.applicationuser.store.service.StoreService; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.repository.StoreRepository; @@ -52,7 +52,7 @@ public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User cust } @Transactional(readOnly = true) - public List getBookmarks(CustomOAuth2User customOAuth2User) { + public List getBookmarks(CustomOAuth2User customOAuth2User) { User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index e092e31e..e134bf28 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.applicationuser.store.service.StoreService; import com.nowait.common.api.ApiUtils; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -37,12 +36,12 @@ public class StoreController { description = "모든 주점을 페이지네이션으로 조회합니다." ) @ApiResponse(responseCode = "200", description = "모든 주점 페이지네이션 조회 성공") - public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable) { + public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { return ResponseEntity .ok() .body( ApiUtils.success( - storeService.getAllStoresByPageAndDeparments(pageable) + storeService.getAllStoresByPageAndDeparments(pageable, customOAuth2User) ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java index e052524b..7047140e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDepartmentReadResponse.java @@ -11,12 +11,12 @@ @Builder public class StoreDepartmentReadResponse { - private List storePageReadDtos; + private List storePageReadResponses; private boolean hasNext; public static StoreDepartmentReadResponse of(List storePageReadResponses, boolean hasNext) { return StoreDepartmentReadResponse.builder() - .storePageReadDtos(storePageReadResponses) + .storePageReadResponses(storePageReadResponses) .hasNext(hasNext) .build(); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java index 6066f75e..ccee3926 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java @@ -15,6 +15,7 @@ @Builder public class StorePageReadResponse { private Long storeId; + private Boolean isBookmark; private Long waitingCount; private Long departmentId; private String departmentName; @@ -29,7 +30,7 @@ public class StorePageReadResponse { private Boolean deleted; private LocalDateTime createdAt; - public static StorePageReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount) { + public static StorePageReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmark) { StoreImageUploadResponse profile = allImages.stream() .filter(image -> image.getImageType() == ImageType.PROFILE) @@ -42,6 +43,7 @@ public static StorePageReadResponse fromEntity(Store store, List bannerImages; private Boolean isActive; private Boolean deleted; private LocalDateTime createdAt; - public static StorePageReadDto fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmark) { + public static StoreSearchResponse fromEntity(Store store, List profileImage, String departmentName, Long waitingCount) { - StoreImageUploadResponse profile = allImages.stream() + StoreImageUploadResponse profile = profileImage.stream() .filter(image -> image.getImageType() == ImageType.PROFILE) .findFirst() .orElse(null); - List banners = allImages.stream() - .filter(image -> image.getImageType() == ImageType.BANNER) - .toList(); - - return StorePageReadDto.builder() + return StoreSearchResponse.builder() .storeId(store.getStoreId()) - .isBookmark(isBookmark) .waitingCount(waitingCount) .departmentId(store.getDepartmentId()) .departmentName(departmentName) .name(store.getName()) - .location(store.getLocation()) - .description(store.getDescription()) - .notice(store.getNotice()) .openTime(store.getOpenTime()) .profileImage(profile) - .bannerImages(banners) .isActive(store.getIsActive()) .deleted(store.getDeleted()) .createdAt(store.getCreatedAt()) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 7900d032..873d713b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -7,20 +7,21 @@ import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; import com.nowait.applicationuser.store.dto.StoreDetailReadResponse; -import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StorePageReadResponse; +import com.nowait.applicationuser.store.dto.StoreSearchResponse; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; +import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; public interface StoreService { - StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable); + StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable, CustomOAuth2User customOAuth2User); StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User); - List searchByKeywordNative(String name); + List searchByKeywordNative(String name); List getStoresByWaitingCount(boolean desc); - List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet); + List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 4743feb2..c02b2259 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -3,6 +3,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,8 +25,8 @@ import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; import com.nowait.applicationuser.store.dto.StoreDetailReadResponse; import com.nowait.applicationuser.store.dto.StoreImageUploadResponse; -import com.nowait.applicationuser.store.dto.StorePageReadDto; import com.nowait.applicationuser.store.dto.StorePageReadResponse; +import com.nowait.applicationuser.store.dto.StoreSearchResponse; import com.nowait.applicationuser.store.dto.StoreWaitingInfo; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; @@ -40,6 +41,7 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domainuserrdb.bookmark.entity.Bookmark; import com.nowait.domainuserrdb.bookmark.repository.BookmarkRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -59,7 +61,10 @@ public class StoreServiceImpl implements StoreService { @Override @Transactional(readOnly = true) - public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable) { + public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable, CustomOAuth2User customOAuth2User) { + + User user = customOAuth2User.getUser(); + // 1) 페이징된 Store 스냅샷 조회 Slice slice = storeRepository.findAllByDeletedFalseOrderByStoreIdAsc(pageable); List stores = slice.getContent(); @@ -73,6 +78,14 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page .distinct() .toList(); + // 2) 사용자 북마크된 storeId 집합 조회 + List storeBookmarkIds = bookmarkRepository.findAllByUser(user) + .stream() + .map(Bookmark::getStore) + .map(Store::getStoreId) + .toList(); + Set bookmarkedSet = new HashSet<>(storeIds); + // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 Map waitingSizeMap = storeIds.stream() .collect(Collectors.toMap( @@ -105,16 +118,21 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page Department::getName )); + List allBookmarks = bookmarkRepository.findStoreIdByUser(user); + Map bookmarkMap = allBookmarks.stream() + .collect(Collectors.toMap( + bookmark -> bookmark.getStore().getStoreId(), + b -> Boolean.TRUE + )); + // 5) Dto 매핑 List content = stores.stream() .map(store -> { - List imgs = imageMap - .getOrDefault(store.getStoreId(), List.of()); - String departmentName = deptNameMap - .getOrDefault(store.getDepartmentId(), "Unknown Department"); - Long waitingCount = - waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount); + List imgs = imageMap.getOrDefault(store.getStoreId(), List.of()); + String departmentName = deptNameMap.getOrDefault(store.getDepartmentId(), "Unknown Department"); + Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); + Boolean isBookmark = bookmarkMap.getOrDefault(store.getStoreId(), false); + return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount, isBookmark); }) .toList(); @@ -153,7 +171,7 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User } @Override - public List searchByKeywordNative(String keyword) { + public List searchByKeywordNative(String keyword) { if (keyword == null || keyword.isBlank()) { throw new StoreParamEmptyException(); } @@ -210,7 +228,7 @@ public List searchByKeywordNative(String keyword) { .getOrDefault(store.getDepartmentId(), "Unknown Department"); Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount); + return StoreSearchResponse.fromEntity(store, imgs, departmentName, waitingCount); }) .toList(); } @@ -285,7 +303,7 @@ private RedisConnection getSafeConnection() { @Override @Transactional(readOnly = true) - public List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet) { + public List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet) { // 1) 페이징된 Store 스냅샷 조회 List stores = storeRepository.findAllByStoreIdInOrderByStoreIdAsc(storeIds); @@ -328,7 +346,7 @@ public List getAllStoresByPageAndDeparments(List storeId )); // 5) Dto 매핑 - List content = stores.stream() + List content = stores.stream() .map(store -> { List imgs = imageMap .getOrDefault(store.getStoreId(), List.of()); @@ -338,7 +356,7 @@ public List getAllStoresByPageAndDeparments(List storeId waitingSizeMap.getOrDefault(store.getStoreId(), 0L); boolean isBookmark = bookmarkedSet.contains(store.getStoreId()); - return StorePageReadDto.fromEntity(store, imgs, departmentName, waitingCount, isBookmark); + return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount, isBookmark); }) .toList(); diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java index 69526299..dd27e0c9 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java @@ -1,6 +1,8 @@ package com.nowait.domainuserrdb.bookmark.repository; +import java.awt.print.Book; import java.util.Collection; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -14,4 +16,6 @@ public interface BookmarkRepository extends JpaRepository { boolean existsByUserAndStore(User user, Store store); Collection findAllByUser(User user); + + List findStoreIdByUser(User user); } From af5b16d04627b965e19eaf99835d3307f40fe80c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 15:53:03 +0900 Subject: [PATCH 344/685] =?UTF-8?q?refactor(Store):=20noticeTile=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreCreateRequest.java | 3 +- .../store/dto/StoreCreateResponse.java | 6 ++-- .../store/dto/StoreReadDto.java | 6 ++-- .../store/dto/StoreUpdateRequest.java | 3 +- .../store/service/StoreServiceImpl.java | 3 +- .../store/dto/StoreDetailReadResponse.java | 6 ++-- .../store/dto/StorePageReadResponse.java | 6 ++-- .../domaincorerdb/store/entity/Store.java | 29 +++++++++++++------ 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java index 4bd9ff23..7650a985 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java @@ -29,7 +29,8 @@ public Store toEntity() { .name(name) .location(location) .description(description) - .notice("") + .noticeTitle("제목을 입력해주세요.") + .noticeContent("내용을 입력해주세요.") .openTime("00002359") .isActive(false) .deleted(false) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java index bfcac36d..725c3159 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java @@ -18,7 +18,8 @@ public class StoreCreateResponse { private String name; private String location; private String description; - private String notice; + private String noticeTitle; + private String noticeContent; private String openTime; private Boolean isActive; private Boolean deleted; @@ -32,7 +33,8 @@ public static StoreCreateResponse fromEntity(Store store) { .name(store.getName()) .location(store.getLocation()) .description(store.getDescription()) - .notice(store.getNotice()) + .noticeTitle(store.getNoticeTitle()) + .noticeContent(store.getNoticeContent()) .openTime(store.getOpenTime()) .isActive(store.getIsActive()) .deleted(store.getDeleted()) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java index 5620cd02..319a12a5 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreReadDto.java @@ -19,7 +19,8 @@ public class StoreReadDto { private String name; private String location; private String description; - private String notice; + private String noticeTitle; + private String noticeContent; private String openTime; private StoreImageUploadResponse profileImage; private List bannerImages; @@ -45,7 +46,8 @@ public static StoreReadDto fromEntity(Store store, List bannerImages; @@ -54,7 +55,8 @@ public static StoreDetailReadResponse fromEntity( .name(store.getName()) .location(store.getLocation()) .description(store.getDescription()) - .notice(store.getNotice()) + .noticeTitle(store.getNoticeTitle()) + .noticeContent(store.getNoticeContent()) .openTime(store.getOpenTime()) .profileImage(profile) .bannerImages(banners) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java index ccee3926..f66b45e9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java @@ -22,7 +22,8 @@ public class StorePageReadResponse { private String name; private String location; private String description; - private String notice; + private String noticeTitle; + private String noticeContent; private String openTime; private StoreImageUploadResponse profileImage; private List bannerImages; @@ -50,7 +51,8 @@ public static StorePageReadResponse fromEntity(Store store, List Date: Tue, 29 Jul 2025 18:01:25 +0900 Subject: [PATCH 345/685] =?UTF-8?q?refactor(Store):=20=EC=9E=85=EC=9E=A5?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=84=20=EC=B2=98=EB=A6=AC=20updatedAt=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/EntryStatusResponseDto.java | 15 ++-------- .../service/ReservationService.java | 27 ++++++----------- .../WaitingUserRedisRepository.java | 6 ++++ .../service/ReservationService.java | 22 ++++++++++++++ .../reservation/entity/Reservation.java | 29 +++---------------- 5 files changed, 43 insertions(+), 56 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java index 1b6ad2cc..5608a458 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java @@ -31,21 +31,12 @@ public class EntryStatusResponseDto { @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") private LocalDateTime createdAt; - @Schema(description = "호출 시각", example = "2025-07-22T16:00:00") - private LocalDateTime calledAt; // 호출 시각 - - @Schema(description = "입장 완료 처리 시각", example = "2025-07-22T16:00:00") - private LocalDateTime confirmedAt; // 완료 시각 - @Schema(description = "웨이팅 취소 시각", example = "2025-07-22T16:00:00") - private LocalDateTime cancelledAt; // 취소 시각 + private LocalDateTime updatedAt; // 취소 시각 @Schema(description = "대기 상태", example = "CALLING") private String status; - @Schema(description = "대기 순번/점수", example = "2.0") - private Double score; - @Schema(description = "호출 메시지", example = "호출 메시지") private String message; @@ -58,9 +49,7 @@ public static EntryStatusResponseDto fromEntity(Reservation r) { .userName(r.getUser().getNickname()) .createdAt(r.getRequestedAt()) .status(r.getStatus().name()) - .calledAt(r.getCalledAt()) - .confirmedAt(r.getConfirmedAt()) - .cancelledAt(r.getCancelledAt()) + .updatedAt(r.getUpdatedAt()) .message(switch (r.getStatus()) { case CALLING -> r.getUser().getNickname() + "님을 호출하였습니다."; case CONFIRMED -> r.getUser().getNickname() + "님의 입장이 완료되었습니다."; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index bbb163da..3ed9fa8d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -99,7 +99,7 @@ public CallGetResponseDto updateReservationStatus(Long reservationId, Reservatio .equals(reservation.getStore().getStoreId())) { throw new ReservationUpdateUnauthorizedException(); } - reservation.updateStatus(requestDto.getStatus()); + reservation.markUpdated(LocalDateTime.now(), requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } @@ -231,14 +231,10 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId); Double score = redisTemplate.opsForZSet().score(queueKey, userId); Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); - Long calledMillis = waitingRedisRepository.getWaitingCalledAt(storeId, userId); LocalDateTime requestedAt = score != null ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() : LocalDateTime.now(); - LocalDateTime calledAt = calledMillis != null - ? Instant.ofEpochMilli(calledMillis).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() - : null; LocalDateTime now = LocalDateTime.now(); switch (newStatus) { @@ -257,8 +253,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .userName(userRepository.getReferenceById(Long.valueOf(userId)).getNickname()) .createdAt(requestedAt) .status("CALLING") - .score(score) - .calledAt(now) + .updatedAt(now) .message("호출되었습니다.") .build(); @@ -276,15 +271,11 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .user(userRepository.getReferenceById(Long.valueOf(userId))) .partySize(partySize) .requestedAt(requestedAt) + .updatedAt(LocalDateTime.now()) .build(); - // 호출 시각 반영 - r.markCalling(calledAt != null ? calledAt : now); - if (newStatus == ReservationStatus.CONFIRMED) { - r.markConfirmed(now); - } else { - r.markCancelled(now); - } + // 호출 시각 반영 + r.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); Reservation saved = reservationRepository.save(r); return EntryStatusResponseDto.fromEntity(saved); } else { @@ -301,8 +292,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me end ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); - existing.markConfirmed(now); - existing.updateStatus(ReservationStatus.CONFIRMED); + existing.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); Reservation saved = reservationRepository.save(existing); return EntryStatusResponseDto.fromEntity(saved); } @@ -320,9 +310,10 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .user(userRepository.getReferenceById(Long.valueOf(userId))) .partySize(partySize) .requestedAt(requestedAt) + .updatedAt(LocalDateTime.now()) .build(); - r.markCalling(calledAt != null ? calledAt : now); - r.markCancelled(now); + + r.markUpdated(LocalDateTime.now(), ReservationStatus.CANCELLED); Reservation saved = reservationRepository.save(r); return EntryStatusResponseDto.fromEntity(saved); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index ccf73412..52631d8d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -4,6 +4,7 @@ import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; @@ -20,6 +21,9 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import lombok.RequiredArgsConstructor; @@ -96,10 +100,12 @@ public boolean removeWaiting(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; String reservationNumberKey = RedisKeyUtils.buildReservationNumberKey(storeId); + redisTemplate.opsForZSet().remove(key, userId); redisTemplate.opsForHash().delete(partyKey, userId); redisTemplate.opsForHash().delete(statusKey, userId); redisTemplate.opsForHash().delete(reservationNumberKey, userId); + return true; } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 04fc4eab..4b3c3edd 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -113,11 +113,33 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { if (storeId == null || userId.trim().isEmpty()) { throw new IllegalArgumentException("Invalid storeId or userId"); } + + String reservationNumber = waitingUserRedisRepository.getReservationId(storeId, userId); + if (reservationNumber == null) { + throw new IllegalArgumentException("Waiting not found"); + } + Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); + Long ts = waitingUserRedisRepository.getWaitingTimestamp(storeId, userId); + // 대기열에서 제거 및 결과 반환 boolean removed = waitingUserRedisRepository.removeWaiting(storeId, userId); if (!removed) { throw new IllegalArgumentException("Waiting not found"); } + + Reservation reservation = Reservation.builder() + .reservationNumber(reservationNumber) + .partySize(partySize) + .status(ReservationStatus.CANCELLED) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.parseLong(userId))) + .updatedAt(LocalDateTime.now()) + .requestedAt(ts != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneId.of("Asia/Seoul")) + : LocalDateTime.now()) + .build(); + + reservationRepository.save(reservation); + return removed; } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index 2f64fb34..da5ec2bf 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -49,14 +49,8 @@ public class Reservation { @Column(name = "requested_at", nullable = false) private LocalDateTime requestedAt; - @Column(name = "called_at", nullable = true) - private LocalDateTime calledAt; // 호출 시각 - - @Column(name = "confirmed_at", nullable = true) - private LocalDateTime confirmedAt; // 확정(입장 완료) 시각 - - @Column(name = "cancelled_at", nullable = true) - private LocalDateTime cancelledAt; // 취소 시각 + @Column(name = "updated_at", nullable = true) + private LocalDateTime updatedAt; @Enumerated(EnumType.STRING) @Column(nullable = false) @@ -65,23 +59,8 @@ public class Reservation { @Column(name = "party_size", nullable = false) private Integer partySize; - public void updateStatus(ReservationStatus status) { + public void markUpdated(LocalDateTime ts, ReservationStatus status) { this.status = status; - } - - // 상태 전환 메서드 - public void markCalling(LocalDateTime ts) { - this.status = ReservationStatus.CALLING; - this.calledAt = ts; - } - - public void markConfirmed(LocalDateTime ts) { - this.status = ReservationStatus.CONFIRMED; - this.confirmedAt = ts; - } - - public void markCancelled(LocalDateTime ts) { - this.status = ReservationStatus.CANCELLED; - this.cancelledAt = ts; + this.updatedAt = ts; } } From b24154c4125a5367d66421c0e2294a75f2283fb9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 18:11:42 +0900 Subject: [PATCH 346/685] =?UTF-8?q?refactor(Store):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingUserRedisRepository.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 52631d8d..6ee8b7f4 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -4,7 +4,6 @@ import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; @@ -21,9 +20,6 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; -import com.nowait.common.enums.ReservationStatus; -import com.nowait.domaincorerdb.reservation.entity.Reservation; -import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import lombok.RequiredArgsConstructor; From 630c6ef39e5c0f6d10ba0d3f6053765bde347d88 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 18:26:48 +0900 Subject: [PATCH 347/685] =?UTF-8?q?refactor(Store):=20=ED=95=99=EA=B3=BC?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=95=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/StoreDetailReadResponse.java | 60 +++++++++++++++++++ .../store/service/StoreService.java | 3 +- .../store/service/StoreServiceImpl.java | 12 +++- 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreDetailReadResponse.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreDetailReadResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreDetailReadResponse.java new file mode 100644 index 00000000..09f4d853 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreDetailReadResponse.java @@ -0,0 +1,60 @@ +package com.nowait.applicationadmin.store.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.nowait.domaincorerdb.store.entity.ImageType; +import com.nowait.domaincorerdb.store.entity.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreDetailReadResponse { + private Long storeId; + private Long departmentId; + private String departmentName; + private String name; + private String location; + private String description; + private String noticeTitle; + private String noticeContent; + private String openTime; + private StoreImageUploadResponse profileImage; + private List bannerImages; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + + public static StoreDetailReadResponse fromEntity(Store store, List allImages, String departmentName) { + + StoreImageUploadResponse profile = allImages.stream() + .filter(image -> image.getImageType() == ImageType.PROFILE) + .findFirst() + .orElse(null); + + List banners = allImages.stream() + .filter(image -> image.getImageType() == ImageType.BANNER) + .toList(); + + return StoreDetailReadResponse.builder() + .storeId(store.getStoreId()) + .departmentId(store.getDepartmentId()) + .departmentName(departmentName) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .noticeTitle(store.getNoticeTitle()) + .noticeContent(store.getNoticeContent()) + .openTime(store.getOpenTime()) + .profileImage(profile) + .bannerImages(banners) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .createdAt(store.getCreatedAt()) + .build(); + } +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java index 5ae0dcdf..2772d8b8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreService.java @@ -2,6 +2,7 @@ import com.nowait.applicationadmin.store.dto.StoreCreateRequest; import com.nowait.applicationadmin.store.dto.StoreCreateResponse; +import com.nowait.applicationadmin.store.dto.StoreDetailReadResponse; import com.nowait.applicationadmin.store.dto.StoreReadDto; import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; import com.nowait.domaincorerdb.user.entity.MemberDetails; @@ -10,7 +11,7 @@ public interface StoreService { StoreCreateResponse createStore(StoreCreateRequest request); - StoreReadDto getStoreByStoreId(Long storeId, MemberDetails memberDetails); + StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails memberDetails); StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, MemberDetails memberDetails); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index 0f01a759..abdc5837 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -8,10 +8,13 @@ import com.nowait.applicationadmin.store.dto.StoreCreateRequest; import com.nowait.applicationadmin.store.dto.StoreCreateResponse; +import com.nowait.applicationadmin.store.dto.StoreDetailReadResponse; import com.nowait.applicationadmin.store.dto.StoreImageUploadResponse; import com.nowait.applicationadmin.store.dto.StoreReadDto; import com.nowait.applicationadmin.store.dto.StoreUpdateRequest; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.department.entity.Department; +import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; @@ -36,6 +39,7 @@ public class StoreServiceImpl implements StoreService { private final StoreRepository storeRepository; private final StoreImageRepository storeImageRepository; private final UserRepository userRepository; + private final DepartmentRepository departmentRepository; @Override @Transactional @@ -51,7 +55,7 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { @Override @Transactional(readOnly = true) - public StoreReadDto getStoreByStoreId(Long storeId, MemberDetails memberDetails) { + public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails memberDetails) { if (storeId == null) throw new StoreParamEmptyException(); User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { @@ -60,12 +64,16 @@ public StoreReadDto getStoreByStoreId(Long storeId, MemberDetails memberDetails) Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); + String departmentName = departmentRepository.findById(store.getDepartmentId()) + .map(Department::getName) + .orElse("Unknown Department"); + List images = storeImageRepository.findByStore(store); List imageDto = images.stream() .map(StoreImageUploadResponse::fromEntity) .toList(); - return StoreReadDto.fromEntity(store, imageDto); + return StoreDetailReadResponse.fromEntity(store, imageDto, departmentName); } @Override From 349960769f2ac97c858fc972731e9d4a33d2f98b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 19:37:48 +0900 Subject: [PATCH 348/685] =?UTF-8?q?refactor(Bookmark):=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=90=20=EC=82=AD=EC=A0=9C=20softDelete=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BookmarkController.java | 13 ++++++--- .../bookmark/service/BookmarkService.java | 29 +++++++++++++------ .../store/service/StoreServiceImpl.java | 6 ++-- .../bookmark/entity/Bookmark.java | 13 +++++++++ .../repository/BookmarkRepository.java | 24 ++++++++++++--- 5 files changed, 65 insertions(+), 20 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java index 2aab9ca0..d57bd64c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java @@ -19,6 +19,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; + @Tag(name = "Bookmark API", description = "북마크 API") @RestController @RequestMapping("/bookmarks") @@ -29,8 +30,9 @@ public class BookmarkController { @PostMapping("/{storeId}") @Operation(summary = "북마크 생성", description = "특정 주점에 대한 북마크 생성") @ApiResponse(responseCode = "201", description = "북마크 생성") - public ResponseEntity createBookmark(@PathVariable Long storeId,@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { - BookmarkCreateResponse response = bookmarkService.createBookmark(storeId,customOAuth2User); + public ResponseEntity createBookmark(@PathVariable Long storeId, + @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + BookmarkCreateResponse response = bookmarkService.createBookmark(storeId, customOAuth2User); return ResponseEntity .status(HttpStatus.CREATED) @@ -40,6 +42,7 @@ public ResponseEntity createBookmark(@PathVariable Long storeId,@Authenticati ) ); } + @GetMapping @Operation(summary = "북마크 조회", description = "내가 북마크한 주점 조회") @ApiResponse(responseCode = "200", description = "북마크 조회") @@ -52,15 +55,17 @@ public ResponseEntity getAllBookmarks(@AuthenticationPrincipal CustomOAuth2Us ) ); } + @DeleteMapping("/{bookmarkId}") @Operation(summary = "북마크 삭제", description = "특정 주점에 대한 북마크 삭제") @ApiResponse(responseCode = "200", description = "북마크 삭제") - public ResponseEntity deleteBookmark(@PathVariable Long bookmarkId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + public ResponseEntity deleteBookmark(@PathVariable Long bookmarkId, + @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { return ResponseEntity .ok() .body( ApiUtils.success( - bookmarkService.deleteBookmark(bookmarkId,customOAuth2User) + bookmarkService.deleteBookmark(bookmarkId, customOAuth2User) ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 744d5201..4b2da27d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.springframework.stereotype.Service; @@ -33,19 +34,29 @@ public class BookmarkService { @Transactional public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User customOAuth2User) { + parameterValidation(storeId, customOAuth2User); Store store = storeRepository.findById(storeId) .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(() -> new EntityNotFoundException("User not found")); - if (bookmarkRepository.existsByUserAndStore(user, store)) { - throw new IllegalArgumentException("already bookmarked"); + Optional isBookmark = bookmarkRepository.findRawByUserAndStoreAndDeletedFalse(user, store); + + if (isBookmark.isPresent()) { + Bookmark bookmark = isBookmark.get(); + if (bookmark.isDeleted()) { + bookmark.restore(); + return BookmarkCreateResponse.fromEntity(bookmarkRepository.save(bookmark)); + } else { + throw new IllegalArgumentException("already bookmarked"); + } } Bookmark bookmark = Bookmark.builder() .store(store) .user(user) + .deleted(false) .build(); return BookmarkCreateResponse.fromEntity(bookmarkRepository.save(bookmark)); @@ -56,7 +67,7 @@ public List getBookmarks(CustomOAuth2User customOAuth2Use User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); - List storeIds = bookmarkRepository.findAllByUser(user) + List storeIds = bookmarkRepository.findAllByUserAndDeletedFalse(user) .stream() .map(Bookmark::getStore) .map(Store::getStoreId) @@ -68,15 +79,15 @@ public List getBookmarks(CustomOAuth2User customOAuth2Use } @Transactional - public String deleteBookmark(Long bookmarkId, CustomOAuth2User customOAuth2User) { - parameterValidation(bookmarkId, customOAuth2User); - Bookmark bookmark = bookmarkRepository.findById(bookmarkId) - .orElseThrow(() -> new EntityNotFoundException(bookmarkId + " bookmark not found.")); + public String deleteBookmark(Long storeId, CustomOAuth2User customOAuth2User) { + parameterValidation(storeId, customOAuth2User); + Bookmark bookmark = bookmarkRepository.findActiveByUserIdAndStoreId(storeId, customOAuth2User.getUserId()) + .orElseThrow(() -> new EntityNotFoundException(storeId + " bookmark not found.")); if (!Objects.equals(bookmark.getUser().getId(), customOAuth2User.getUserId())) { throw new IllegalArgumentException("you can only delete your own bookmark"); } - bookmarkRepository.delete(bookmark); - return "Bookmark ID " + bookmarkId + " deleted."; + bookmark.softDelete(); + return "Bookmark ID " + storeId + " deleted."; } private static void parameterValidation(Long storeId, CustomOAuth2User customOAuth2User) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index c02b2259..6082ae37 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -79,7 +79,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page .toList(); // 2) 사용자 북마크된 storeId 집합 조회 - List storeBookmarkIds = bookmarkRepository.findAllByUser(user) + List storeBookmarkIds = bookmarkRepository.findAllByUserAndDeletedFalse(user) .stream() .map(Bookmark::getStore) .map(Store::getStoreId) @@ -118,7 +118,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page Department::getName )); - List allBookmarks = bookmarkRepository.findStoreIdByUser(user); + List allBookmarks = bookmarkRepository.findStoreIdByUserAndDeletedFalse(user); Map bookmarkMap = allBookmarks.stream() .collect(Collectors.toMap( bookmark -> bookmark.getStore().getStoreId(), @@ -155,7 +155,7 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User .map(Department::getName) .orElse("Unknown Department"); - boolean isBookmark = bookmarkRepository.existsByUserAndStore(user, store); + boolean isBookmark = bookmarkRepository.existsByUserAndStoreAndDeletedFalse(user, store); // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 String key = "waiting:" + storeId; diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java index 1f5b3fa2..c36a0dd5 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/entity/Bookmark.java @@ -1,9 +1,12 @@ package com.nowait.domainuserrdb.bookmark.entity; +import java.time.LocalDateTime; + import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.user.entity.User; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -43,4 +46,14 @@ public class Bookmark extends BaseTimeEntity { @JoinColumn(name = "store_id") private Store store; + @Column(nullable = false) + private boolean deleted = false; + + public void softDelete() { + this.deleted = true; + } + + public void restore() { + this.deleted = false; + } } diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java index dd27e0c9..53914f5c 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java @@ -1,10 +1,12 @@ package com.nowait.domainuserrdb.bookmark.repository; -import java.awt.print.Book; import java.util.Collection; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.nowait.domaincorerdb.store.entity.Store; @@ -13,9 +15,23 @@ @Repository public interface BookmarkRepository extends JpaRepository { - boolean existsByUserAndStore(User user, Store store); + boolean existsByUserAndStoreAndDeletedFalse(User user, Store store); - Collection findAllByUser(User user); + Optional findRawByUserAndStoreAndDeletedFalse(User user, Store store); - List findStoreIdByUser(User user); + Collection findAllByUserAndDeletedFalse(User user); + + List findStoreIdByUserAndDeletedFalse(User user); + + @Query(""" + select b + from Bookmark b + where b.user.id = :userId + and b.store.storeId = :storeId + and b.deleted = false + """) + Optional findActiveByUserIdAndStoreId( + @Param("storeId") Long storeId, + @Param("userId") Long userId + ); } From 0f6e636e617bb2b3a2ed7ce9ea63dab85edde6ad Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 20:19:08 +0900 Subject: [PATCH 349/685] =?UTF-8?q?refactor(storePayment):=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=97=B0=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 2 +- .../dto/StorePaymentCreateRequest.java | 2 ++ .../dto/StorePaymentCreateResponse.java | 8 +++++--- .../storePayment/dto/StorePaymentReadDto.java | 2 ++ .../dto/StorePaymentUpdateRequest.java | 1 + .../service/StorePaymentServiceImpl.java | 3 ++- .../storePayment/entity/StorePayment.java | 14 ++++++++++---- 7 files changed, 23 insertions(+), 9 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 82c1784a..7c926dd6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -34,7 +34,7 @@ public class StorePaymentController { private final StorePaymentService storePaymentService; @PostMapping("/create") - @Operation(summary = "주점 결제 Url 등록 및 생성", description = "새로운 주점 결제 정보를 생성합니다.") + @Operation(summary = "주점 결제 정보 연동 및 생성", description = "새로운 주점 결제 정보를 생성합니다.") @ApiResponse(responseCode = "201", description = "주점 결제 정보 생성 성공") public ResponseEntity createStorePayment(@Valid @RequestBody StorePaymentCreateRequest request, @AuthenticationPrincipal MemberDetails memberDetails) { StorePaymentCreateResponse response = storePaymentService.createStorePayment(request, memberDetails); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java index 13801ccb..3b6e7f4e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateRequest.java @@ -15,6 +15,7 @@ public class StorePaymentCreateRequest { private String tossUrl; private String kakaoPayUrl; private String naverPayUrl; + private String accountNumber; public StorePayment toEntity(Long storeId) { return StorePayment.builder() @@ -22,6 +23,7 @@ public StorePayment toEntity(Long storeId) { .tossUrl(tossUrl) .kakaoPayUrl(kakaoPayUrl) .naverPayUrl(naverPayUrl) + .accountNumber(accountNumber) .build(); } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java index 47bead90..2c2cc785 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentCreateResponse.java @@ -18,15 +18,17 @@ public class StorePaymentCreateResponse { private String tossUrl; private String kakaoPayUrl; private String naverPayUrl; + private String accountNumber; private LocalDateTime createdAt; public static StorePaymentCreateResponse fromEntity(StorePayment storePayment) { return StorePaymentCreateResponse.builder() .paymentMethodId(storePayment.getPaymentMethodId()) .storeId(storePayment.getStoreId()) - .tossUrl(storePayment.getTossUrl()) - .kakaoPayUrl(storePayment.getKakaoPayUrl()) - .naverPayUrl(storePayment.getNaverPayUrl()) + .tossUrl(storePayment.getTossUrl() != null ? storePayment.getTossUrl() : "") + .kakaoPayUrl(storePayment.getKakaoPayUrl() != null ? storePayment.getKakaoPayUrl() : "") + .naverPayUrl(storePayment.getNaverPayUrl() != null ? storePayment.getNaverPayUrl() : "") + .accountNumber(storePayment.getAccountNumber() != null ? storePayment.getAccountNumber() : "") .createdAt(storePayment.getCreatedAt()) .build(); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java index 04f2e0f6..bbd634e7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentReadDto.java @@ -17,6 +17,7 @@ public class StorePaymentReadDto { private String tossUrl; private String kakaoPayUrl; private String naverPayUrl; + private String accountNumber; private LocalDateTime createdAt; public static StorePaymentReadDto fromEntity(StorePayment storePayment) { @@ -26,6 +27,7 @@ public static StorePaymentReadDto fromEntity(StorePayment storePayment) { .tossUrl(storePayment.getTossUrl()) .kakaoPayUrl(storePayment.getKakaoPayUrl()) .naverPayUrl(storePayment.getNaverPayUrl()) + .accountNumber(storePayment.getAccountNumber()) .createdAt(storePayment.getCreatedAt()) .build(); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java index 50320aab..2c2d0220 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/dto/StorePaymentUpdateRequest.java @@ -13,4 +13,5 @@ public class StorePaymentUpdateRequest { private String tossUrl; private String kakaoPayUrl; private String naverPayUrl; + private String accountNumber; } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index ff95015d..67db6c18 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -81,7 +81,8 @@ public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, storePayment.updatePaymentMethodUrl( request.getTossUrl(), request.getKakaoPayUrl(), - request.getNaverPayUrl() + request.getNaverPayUrl(), + request.getAccountNumber() ); storePaymentRepository.save(storePayment); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java index effb50d8..aebd8b6a 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/entity/StorePayment.java @@ -34,24 +34,30 @@ public class StorePayment extends BaseTimeEntity { @Column(name = "toss_url",length = 500) private String tossUrl; - @Column(name = "kakao_pay_url", length = 500) + @Column(name = "kakao_pay_url", nullable = true, length = 500) private String kakaoPayUrl; - @Column(name = "naver_pay_url", length = 500) + @Column(name = "naver_pay_url", nullable = true, length = 500) private String naverPayUrl; - public StorePayment(LocalDateTime createdAt, Long paymentMethodId, Long storeId, String tossUrl, String kakaoPayUrl, String naverPayUrl) { + @Column(name = "account_number", nullable = true, length = 45) + private String accountNumber; + + public StorePayment(LocalDateTime createdAt, Long paymentMethodId, Long storeId, String tossUrl, String kakaoPayUrl, String naverPayUrl, String accountNumber) { super(createdAt); this.paymentMethodId = paymentMethodId; this.storeId = storeId; this.tossUrl = tossUrl; this.kakaoPayUrl = kakaoPayUrl; this.naverPayUrl = naverPayUrl; + this.accountNumber = accountNumber; } - public void updatePaymentMethodUrl(String tossUrl, String kakaoPayUrl, String naverPayUrl) { + public void updatePaymentMethodUrl(String tossUrl, String kakaoPayUrl, String naverPayUrl, String accountNumber) { if (tossUrl != null) this.tossUrl = tossUrl; if (kakaoPayUrl != null) this.kakaoPayUrl = kakaoPayUrl; if (naverPayUrl != null) this.naverPayUrl = naverPayUrl; + if (accountNumber != null) this.accountNumber = accountNumber; } } + From e91481c87451a0461f23f37b6ba0888138c3e2fa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 20:43:23 +0900 Subject: [PATCH 350/685] =?UTF-8?q?fix(Bookmark):=20=EB=B6=81=EB=A7=88?= =?UTF-8?q?=ED=81=AC=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/service/BookmarkService.java | 19 +++++++++++++------ .../repository/BookmarkRepository.java | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 4b2da27d..d2c383af 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -13,6 +13,7 @@ import com.nowait.applicationuser.store.dto.StorePageReadResponse; import com.nowait.applicationuser.store.service.StoreService; import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -37,17 +38,18 @@ public BookmarkCreateResponse createBookmark(Long storeId, CustomOAuth2User cust parameterValidation(storeId, customOAuth2User); Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + .orElseThrow(StoreNotFoundException::new); User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(() -> new EntityNotFoundException("User not found")); + .orElseThrow(UserNotFoundException::new); - Optional isBookmark = bookmarkRepository.findRawByUserAndStoreAndDeletedFalse(user, store); + Optional isBookmark = bookmarkRepository.findByUserAndStore(user, store); if (isBookmark.isPresent()) { Bookmark bookmark = isBookmark.get(); if (bookmark.isDeleted()) { bookmark.restore(); - return BookmarkCreateResponse.fromEntity(bookmarkRepository.save(bookmark)); + Bookmark restored = bookmarkRepository.save(bookmark); + return BookmarkCreateResponse.fromEntity(bookmarkRepository.save(restored)); } else { throw new IllegalArgumentException("already bookmarked"); } @@ -81,8 +83,13 @@ public List getBookmarks(CustomOAuth2User customOAuth2Use @Transactional public String deleteBookmark(Long storeId, CustomOAuth2User customOAuth2User) { parameterValidation(storeId, customOAuth2User); - Bookmark bookmark = bookmarkRepository.findActiveByUserIdAndStoreId(storeId, customOAuth2User.getUserId()) - .orElseThrow(() -> new EntityNotFoundException(storeId + " bookmark not found.")); + User user = customOAuth2User.getUser(); + Store store = storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + + Bookmark bookmark = bookmarkRepository.findByUserAndStoreAndDeletedFalse(user, store) + .orElseThrow(() -> new EntityNotFoundException(storeId + " 활성화된 bookmark가 없습니다.")); + if (!Objects.equals(bookmark.getUser().getId(), customOAuth2User.getUserId())) { throw new IllegalArgumentException("you can only delete your own bookmark"); } diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java index 53914f5c..bac91e52 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java @@ -17,7 +17,9 @@ public interface BookmarkRepository extends JpaRepository { boolean existsByUserAndStoreAndDeletedFalse(User user, Store store); - Optional findRawByUserAndStoreAndDeletedFalse(User user, Store store); + Optional findByUserAndStoreAndDeletedFalse(User user, Store store); + + Optional findByUserAndStore(User user, Store store); Collection findAllByUserAndDeletedFalse(User user); From 1a0a3045560ed435df9450a262f5b417e9e128ae Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 21:04:34 +0900 Subject: [PATCH 351/685] =?UTF-8?q?fix(Bookmark):=20=EB=B6=81=EB=A7=88?= =?UTF-8?q?=ED=81=AC=20=EC=82=AD=EC=A0=9C=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/controller/BookmarkController.java | 7 ++++--- .../bookmark/service/BookmarkService.java | 3 ++- .../exception/GlobalExceptionHandler.java | 10 ++++++++++ .../java/com/nowait/common/exception/ErrorMessage.java | 1 + .../bookmark/exception/BookmarkNotFoundException.java | 9 +++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java index d57bd64c..5c1e9268 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java @@ -56,16 +56,17 @@ public ResponseEntity getAllBookmarks(@AuthenticationPrincipal CustomOAuth2Us ); } - @DeleteMapping("/{bookmarkId}") + @DeleteMapping("/{storeId}") @Operation(summary = "북마크 삭제", description = "특정 주점에 대한 북마크 삭제") @ApiResponse(responseCode = "200", description = "북마크 삭제") - public ResponseEntity deleteBookmark(@PathVariable Long bookmarkId, + public ResponseEntity deleteBookmark(@PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + String response = bookmarkService.deleteBookmark(storeId, customOAuth2User); return ResponseEntity .ok() .body( ApiUtils.success( - bookmarkService.deleteBookmark(bookmarkId, customOAuth2User) + response ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index d2c383af..61649737 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -19,6 +19,7 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.bookmark.entity.Bookmark; +import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; import com.nowait.domainuserrdb.bookmark.repository.BookmarkRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -88,7 +89,7 @@ public String deleteBookmark(Long storeId, CustomOAuth2User customOAuth2User) { .orElseThrow(StoreNotFoundException::new); Bookmark bookmark = bookmarkRepository.findByUserAndStoreAndDeletedFalse(user, store) - .orElseThrow(() -> new EntityNotFoundException(storeId + " 활성화된 bookmark가 없습니다.")); + .orElseThrow(BookmarkNotFoundException::new); if (!Objects.equals(bookmark.getUser().getId(), customOAuth2User.getUserId())) { throw new IllegalArgumentException("you can only delete your own bookmark"); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index ef46c2a0..cece9625 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -37,6 +37,7 @@ import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkOwnerMismatchException; import com.nowait.domainuserrdb.bookmark.exception.DuplicateBookmarkException; @@ -146,6 +147,15 @@ public ErrorResponse bookmarkOwnerMismatchException(BookmarkOwnerMismatchExcepti return new ErrorResponse(e.getMessage(), NOT_OWN_BOOKMARK.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(BookmarkNotFoundException.class) + public ErrorResponse handleBookmarkNotFoundException( + BookmarkNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("handleBookmarkNotFoundException", e); + return new ErrorResponse(e.getMessage(), BOOKMARK_NOT_FOUND.getCode()); + } + @ResponseStatus(NOT_FOUND) @ExceptionHandler(UserNotFoundException.class) public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index db722455..979449ca 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -32,6 +32,7 @@ public enum ErrorMessage { // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), NOT_OWN_BOOKMARK("해당 주점은 다른 사용자가 북마크한 주점입니다.", "bookmark002"), + BOOKMARK_NOT_FOUND("이미 북마크 삭제된 주점입니다.", "bookmark002"), // menu MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"), diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java new file mode 100644 index 00000000..ae29fd90 --- /dev/null +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.domainuserrdb.bookmark.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class BookmarkNotFoundException extends RuntimeException { + public BookmarkNotFoundException() { + super(ErrorMessage.BOOKMARK_NOT_FOUND.getMessage()); + } +} From 69e3911255ee1009f3a0a59824b9d4f588844b1e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 21:14:54 +0900 Subject: [PATCH 352/685] =?UTF-8?q?fix(storePayment):=20accountNumber=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/storepayment/dto/StorePaymentReadDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java index e0af8aab..c96e01c2 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/dto/StorePaymentReadDto.java @@ -17,6 +17,7 @@ public class StorePaymentReadDto { private String tossUrl; private String kakaoPayUrl; private String naverPayUrl; + private String accountNumber; private LocalDateTime createdAt; public static StorePaymentReadDto fromEntity(StorePayment storePayment) { @@ -26,6 +27,7 @@ public static StorePaymentReadDto fromEntity(StorePayment storePayment) { .tossUrl(storePayment.getTossUrl()) .kakaoPayUrl(storePayment.getKakaoPayUrl()) .naverPayUrl(storePayment.getNaverPayUrl()) + .accountNumber(storePayment.getAccountNumber()) .createdAt(storePayment.getCreatedAt()) .build(); } From d560c179ad355ae26c6869bd954da7b60fde8ece Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 23:39:31 +0900 Subject: [PATCH 353/685] =?UTF-8?q?refactor(Bookmark):=20bookmarkId=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/service/BookmarkService.java | 20 +++-- .../store/dto/StoreDetailReadResponse.java | 4 +- .../store/dto/StorePageReadResponse.java | 4 +- .../store/service/StoreService.java | 3 +- .../store/service/StoreServiceImpl.java | 84 ++++++++++--------- .../store/repository/StoreRepository.java | 2 - .../repository/BookmarkRepository.java | 13 --- 7 files changed, 66 insertions(+), 64 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java index 61649737..ed20348a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/service/BookmarkService.java @@ -1,10 +1,11 @@ package com.nowait.applicationuser.bookmark.service; -import java.util.HashSet; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,11 +20,10 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.bookmark.entity.Bookmark; -import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; +import com.nowait.domainuserrdb.bookmark.exception.AlreadyDeletedBookmarkException; import com.nowait.domainuserrdb.bookmark.repository.BookmarkRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @Service @@ -70,15 +70,21 @@ public List getBookmarks(CustomOAuth2User customOAuth2Use User user = userRepository.findById(customOAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); + Collection allBookmarks = bookmarkRepository.findAllByUserAndDeletedFalse(user); + List storeIds = bookmarkRepository.findAllByUserAndDeletedFalse(user) .stream() .map(Bookmark::getStore) .map(Store::getStoreId) .toList(); - Set bookmarkedSet = new HashSet<>(storeIds); + Map bookmarkMap = allBookmarks.stream() + .collect(Collectors.toMap( + b -> b.getStore().getStoreId(), + Bookmark::getId + )); - return storeService.getAllStoresByPageAndDeparments(storeIds, bookmarkedSet); + return storeService.getAllStoresByPageAndDeparments(storeIds, bookmarkMap); } @Transactional @@ -89,7 +95,7 @@ public String deleteBookmark(Long storeId, CustomOAuth2User customOAuth2User) { .orElseThrow(StoreNotFoundException::new); Bookmark bookmark = bookmarkRepository.findByUserAndStoreAndDeletedFalse(user, store) - .orElseThrow(BookmarkNotFoundException::new); + .orElseThrow(AlreadyDeletedBookmarkException::new); if (!Objects.equals(bookmark.getUser().getId(), customOAuth2User.getUserId())) { throw new IllegalArgumentException("you can only delete your own bookmark"); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java index b2ab0295..e84c21c1 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java @@ -15,6 +15,7 @@ @Builder public class StoreDetailReadResponse { private Long storeId; + private Long bookmarkId; private Boolean isBookmark; private Long waitingCount; private Boolean isWaiting; @@ -33,7 +34,7 @@ public class StoreDetailReadResponse { private LocalDateTime createdAt; public static StoreDetailReadResponse fromEntity( - Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmark, Boolean isWaiting + Store store, Long bookmarkId, List allImages, String departmentName, Long waitingCount, Boolean isBookmark, Boolean isWaiting ) { StoreImageUploadResponse profile = allImages.stream() @@ -47,6 +48,7 @@ public static StoreDetailReadResponse fromEntity( return StoreDetailReadResponse.builder() .storeId(store.getStoreId()) + .bookmarkId(bookmarkId) .isBookmark(isBookmark) .waitingCount(waitingCount) .isWaiting(isWaiting) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java index f66b45e9..cabee501 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java @@ -15,6 +15,7 @@ @Builder public class StorePageReadResponse { private Long storeId; + private Long bookmarkId; private Boolean isBookmark; private Long waitingCount; private Long departmentId; @@ -31,7 +32,7 @@ public class StorePageReadResponse { private Boolean deleted; private LocalDateTime createdAt; - public static StorePageReadResponse fromEntity(Store store, List allImages, String departmentName, Long waitingCount, Boolean isBookmark) { + public static StorePageReadResponse fromEntity(Store store, Long bookmarkId, List allImages, String departmentName, Long waitingCount, Boolean isBookmark) { StoreImageUploadResponse profile = allImages.stream() .filter(image -> image.getImageType() == ImageType.PROFILE) @@ -44,6 +45,7 @@ public static StorePageReadResponse fromEntity(Store store, List getStoresByWaitingCount(boolean desc); - List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet); + List getAllStoresByPageAndDeparments(List storeIds, Map bookmarkMap); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 6082ae37..c49b4718 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -1,11 +1,14 @@ package com.nowait.applicationuser.store.service; +import java.awt.print.Book; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -42,6 +45,7 @@ import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import com.nowait.domainuserrdb.bookmark.entity.Bookmark; +import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; import com.nowait.domainuserrdb.bookmark.repository.BookmarkRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -56,12 +60,12 @@ public class StoreServiceImpl implements StoreService { private final DepartmentRepository departmentRepository; private final StringRedisTemplate redisTemplate; private final BookmarkRepository bookmarkRepository; - private final UserRepository userRepository; private final WaitingUserRedisRepository waitingRepo; @Override @Transactional(readOnly = true) - public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable, CustomOAuth2User customOAuth2User) { + public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable, + CustomOAuth2User customOAuth2User) { User user = customOAuth2User.getUser(); @@ -78,28 +82,20 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page .distinct() .toList(); - // 2) 사용자 북마크된 storeId 집합 조회 - List storeBookmarkIds = bookmarkRepository.findAllByUserAndDeletedFalse(user) - .stream() - .map(Bookmark::getStore) - .map(Store::getStoreId) - .toList(); - Set bookmarkedSet = new HashSet<>(storeIds); - // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 Map waitingSizeMap = storeIds.stream() - .collect(Collectors.toMap( + .collect(Collectors.toMap( Function.identity(), - storeId -> { - String key = "waiting:" + storeId; - try { - return redisTemplate.opsForZSet().zCard(key); - } catch (Exception e) { - return 0L; // Redis 접근 실패 시 0으로 처리 - } + storeId -> { + String key = "waiting:" + storeId; + try { + return redisTemplate.opsForZSet().zCard(key); + } catch (Exception e) { + return 0L; // Redis 접근 실패 시 0으로 처리 + } - } - )); + } + )); // 3) 각 StoreId에 해당하는 이미지 조회 List allImages = storeImageRepository.findByStore_StoreIdIn(storeIds); @@ -109,7 +105,6 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page StoreImageUploadResponse::getStoreId )); - // 4) 각 DepartmentId에 해당하는 이름 조회 List allDepts = departmentRepository.findAllById(deptIds); Map deptNameMap = allDepts.stream() @@ -118,21 +113,24 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page Department::getName )); - List allBookmarks = bookmarkRepository.findStoreIdByUserAndDeletedFalse(user); - Map bookmarkMap = allBookmarks.stream() + // 5) 북마크 조회 + Collection allBookmarks = bookmarkRepository.findAllByUserAndDeletedFalse(user); + Map bookmarkIdMap = allBookmarks.stream() .collect(Collectors.toMap( - bookmark -> bookmark.getStore().getStoreId(), - b -> Boolean.TRUE + b -> b.getStore().getStoreId(), + Bookmark::getId )); // 5) Dto 매핑 List content = stores.stream() .map(store -> { + Long bookmarkId = bookmarkIdMap.get(store.getStoreId()); + boolean isBookmark = bookmarkId != null; List imgs = imageMap.getOrDefault(store.getStoreId(), List.of()); String departmentName = deptNameMap.getOrDefault(store.getDepartmentId(), "Unknown Department"); Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - Boolean isBookmark = bookmarkMap.getOrDefault(store.getStoreId(), false); - return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount, isBookmark); + + return StorePageReadResponse.fromEntity(store, bookmarkId, imgs, departmentName, waitingCount, isBookmark); }) .toList(); @@ -144,9 +142,10 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Override @Transactional(readOnly = true) public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User) { - if (storeId == null) throw new StoreParamEmptyException(); - User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(UserNotFoundException::new); + + if (storeId == null) + throw new StoreParamEmptyException(); + User user = customOAuth2User.getUser(); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -155,7 +154,9 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User .map(Department::getName) .orElse("Unknown Department"); - boolean isBookmark = bookmarkRepository.existsByUserAndStoreAndDeletedFalse(user, store); + Optional bookmark = bookmarkRepository.findByUserAndStoreAndDeletedFalse(user, store); + boolean isBookmark = bookmark.isPresent(); + Long bookmarkId = isBookmark ? bookmark.get().getId() : null; // 2-1) Redis에서 각 Store의 웨이팅 사이즈 조회 String key = "waiting:" + storeId; @@ -167,7 +168,8 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User .map(StoreImageUploadResponse::fromEntity) .toList(); - return StoreDetailReadResponse.fromEntity(store, imageDto, departmentName, waitingSize, isBookmark, userWaiting); + return StoreDetailReadResponse.fromEntity(store, bookmarkId, imageDto, departmentName, waitingSize, isBookmark, + userWaiting); } @Override @@ -255,7 +257,8 @@ public List getStoresByWaitingCount(boolean desc) { // zset 타입인지 확인 DataType type = redisTemplate.type(key); - if (type == null || !"zset".equals(type.code())) continue; + if (type == null || !"zset".equals(type.code())) + continue; Long count = redisTemplate.opsForZSet().zCard(key); String storeId = key.replace(PREFIX, ""); @@ -284,7 +287,8 @@ public List getStoresByWaitingCount(boolean desc) { // 정렬 로직: 대기 인원 기준 desc/asc Comparator comparator = Comparator.comparing(StoreWaitingInfo::getWaitingCount); - if (desc) comparator = comparator.reversed(); + if (desc) + comparator = comparator.reversed(); result.sort(comparator); return result.stream() @@ -300,10 +304,10 @@ private RedisConnection getSafeConnection() { return factory.getConnection(); } - @Override @Transactional(readOnly = true) - public List getAllStoresByPageAndDeparments(List storeIds, Set bookmarkedSet) { + public List getAllStoresByPageAndDeparments(List storeIds, + Map bookmarkMap) { // 1) 페이징된 Store 스냅샷 조회 List stores = storeRepository.findAllByStoreIdInOrderByStoreIdAsc(storeIds); @@ -336,7 +340,6 @@ public List getAllStoresByPageAndDeparments(List st StoreImageUploadResponse::getStoreId )); - // 4) 각 DepartmentId에 해당하는 이름 조회 List allDepts = departmentRepository.findAllById(deptIds); Map deptNameMap = allDepts.stream() @@ -348,15 +351,18 @@ public List getAllStoresByPageAndDeparments(List st // 5) Dto 매핑 List content = stores.stream() .map(store -> { + Long bookmarkId = bookmarkMap.get(store.getStoreId()); + boolean isBookmark = bookmarkId != null; + List imgs = imageMap .getOrDefault(store.getStoreId(), List.of()); String departmentName = deptNameMap .getOrDefault(store.getDepartmentId(), "Unknown Department"); Long waitingCount = waitingSizeMap.getOrDefault(store.getStoreId(), 0L); - boolean isBookmark = bookmarkedSet.contains(store.getStoreId()); - return StorePageReadResponse.fromEntity(store, imgs, departmentName, waitingCount, isBookmark); + return StorePageReadResponse.fromEntity(store, bookmarkId, imgs, departmentName, waitingCount, + isBookmark); }) .toList(); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index 05e689fc..921a0ad2 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -16,8 +16,6 @@ @Repository public interface StoreRepository extends JpaRepository, StoreCustomRepository { - List findAllByDeletedFalse(); - Optional findByStoreIdAndDeletedFalse(Long storeId); Slice findAllByDeletedFalseOrderByStoreIdAsc(Pageable pageable); diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java index bac91e52..fc9e0a23 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/repository/BookmarkRepository.java @@ -15,7 +15,6 @@ @Repository public interface BookmarkRepository extends JpaRepository { - boolean existsByUserAndStoreAndDeletedFalse(User user, Store store); Optional findByUserAndStoreAndDeletedFalse(User user, Store store); @@ -24,16 +23,4 @@ public interface BookmarkRepository extends JpaRepository { Collection findAllByUserAndDeletedFalse(User user); List findStoreIdByUserAndDeletedFalse(User user); - - @Query(""" - select b - from Bookmark b - where b.user.id = :userId - and b.store.storeId = :storeId - and b.deleted = false - """) - Optional findActiveByUserIdAndStoreId( - @Param("storeId") Long storeId, - @Param("userId") Long userId - ); } From 61ff34d67b9efad7603ab33c589194deeb278aa3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 29 Jul 2025 23:39:54 +0900 Subject: [PATCH 354/685] =?UTF-8?q?feat(Bookmark):=20bookmark=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 12 +++++++++++- .../com/nowait/common/exception/ErrorMessage.java | 4 +++- .../exception/AlreadyDeletedBookmarkException.java | 7 +++++++ .../exception/BookmarkNotFoundException.java | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/AlreadyDeletedBookmarkException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index cece9625..919e986d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -37,6 +37,7 @@ import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domainuserrdb.bookmark.exception.AlreadyDeletedBookmarkException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkOwnerMismatchException; import com.nowait.domainuserrdb.bookmark.exception.DuplicateBookmarkException; @@ -153,7 +154,16 @@ public ErrorResponse handleBookmarkNotFoundException( BookmarkNotFoundException e, WebRequest request) { alarm(e, request); log.error("handleBookmarkNotFoundException", e); - return new ErrorResponse(e.getMessage(), BOOKMARK_NOT_FOUND.getCode()); + return new ErrorResponse(e.getMessage(), NOT_FOUND_BOOKMARK.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(AlreadyDeletedBookmarkException.class) + public ErrorResponse handleAlreadyDeleteBookmark( + AlreadyDeletedBookmarkException e, WebRequest request) { + alarm(e, request); + log.error("AlreadyDeletedBookmarkException", e); + return new ErrorResponse(e.getMessage(), ALREADY_DELETED_BOOKMARK.getCode()); } @ResponseStatus(NOT_FOUND) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 979449ca..6b6b92b9 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -32,7 +32,9 @@ public enum ErrorMessage { // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), NOT_OWN_BOOKMARK("해당 주점은 다른 사용자가 북마크한 주점입니다.", "bookmark002"), - BOOKMARK_NOT_FOUND("이미 북마크 삭제된 주점입니다.", "bookmark002"), + NOT_FOUND_BOOKMARK("북마크를 찾을 수 없습니다", "bookmark003"), + ALREADY_DELETED_BOOKMARK("이미 삭제된 북마크입니다.", "bookmark003"), + // menu MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"), diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/AlreadyDeletedBookmarkException.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/AlreadyDeletedBookmarkException.java new file mode 100644 index 00000000..1e48a5e8 --- /dev/null +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/AlreadyDeletedBookmarkException.java @@ -0,0 +1,7 @@ +package com.nowait.domainuserrdb.bookmark.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class AlreadyDeletedBookmarkException extends RuntimeException { + public AlreadyDeletedBookmarkException() { super(ErrorMessage.ALREADY_DELETED_BOOKMARK.getMessage()); } +} diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java index ae29fd90..936296d8 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/bookmark/exception/BookmarkNotFoundException.java @@ -4,6 +4,6 @@ public class BookmarkNotFoundException extends RuntimeException { public BookmarkNotFoundException() { - super(ErrorMessage.BOOKMARK_NOT_FOUND.getMessage()); + super(ErrorMessage.NOT_FOUND_BOOKMARK.getMessage()); } } From 8aa41fcf50b9aa81da79f65d300fe6e24b3a1aa2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 00:01:32 +0900 Subject: [PATCH 355/685] =?UTF-8?q?refactor(Bookmark):=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/common/exception/ErrorMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 6b6b92b9..b8a416da 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -33,7 +33,7 @@ public enum ErrorMessage { DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), NOT_OWN_BOOKMARK("해당 주점은 다른 사용자가 북마크한 주점입니다.", "bookmark002"), NOT_FOUND_BOOKMARK("북마크를 찾을 수 없습니다", "bookmark003"), - ALREADY_DELETED_BOOKMARK("이미 삭제된 북마크입니다.", "bookmark003"), + ALREADY_DELETED_BOOKMARK("이미 삭제된 북마크입니다.", "bookmark004"), // menu From 8e99cb02e52915073ae9246af1abee40f199217a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 11:26:18 +0900 Subject: [PATCH 356/685] =?UTF-8?q?refactor(Reservation):=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EC=9E=84=20=EC=9D=B8=EC=A6=9D=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20memberDetails=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/controller/ReservationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index 6ec51e54..d962de4c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -53,7 +53,7 @@ public ResponseEntity getCompletedReservationList( @PathVariable Long storeId, @AuthenticationPrincipal MemberDetails memberDetails ) { - List response = reservationService.getCompletedWaitingUserDetails(storeId); + List response = reservationService.getCompletedWaitingUserDetails(storeId, memberDetails); return ResponseEntity .ok() .body( From 8857409ab726b6ca9a164d3080233bcb96a3385b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 11:26:31 +0900 Subject: [PATCH 357/685] =?UTF-8?q?refactor(Reservation):=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ReservationRepository.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 61efa124..17e8c36b 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -15,26 +15,12 @@ @Repository public interface ReservationRepository extends JpaRepository { List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); - boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses); - - Optional findByStore_StoreIdAndUserId(Long storeId, Long userId); - Optional findByStore_StoreIdAndUserIdAndRequestedAtBetween( - Long storeId, Long userId, LocalDateTime start, LocalDateTime end); + boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses); Optional findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( Long storeId, Long userId, List statuses, LocalDateTime start, LocalDateTime end); List findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( Long storeId, List statuses, LocalDateTime start, LocalDateTime end); - Optional findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( - Long storeId, - Long userId, - List statuses, - LocalDateTime start, - LocalDateTime end - ); - - List findAllByStore_StoreId(Long storeId); - } From ff8937222375961fd0d21b20393cb76c9ab8af72 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 11:28:17 +0900 Subject: [PATCH 358/685] =?UTF-8?q?refactor(Reservation):=20findTodayWaiti?= =?UTF-8?q?ng=20=EA=B3=B5=ED=86=B5=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=20=EB=B0=8F=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 3ed9fa8d..96f2e7b9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -5,7 +5,6 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; -import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -176,41 +175,17 @@ public List getAllWaitingUserDetails(Long storeId) { // 완료 or 취소 처리된 대기 리스트 조회 @Transactional(readOnly = true) - public List getCompletedWaitingUserDetails(Long storeId) { - List reservations = reservationRepository.findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( - storeId, - List.of(ReservationStatus.CONFIRMED, ReservationStatus.CANCELLED), - LocalDate.now().atStartOfDay(), - LocalDate.now().atTime(LocalTime.MAX)); + public List getCompletedWaitingUserDetails(Long storeId, MemberDetails memberDetails) { + authorize(storeId, memberDetails); + List reservations = findTodayWaiting(storeId); - return reservations.stream() - .map(r -> WaitingUserResponse.fromEntity(r)) - .toList(); - } - - private User authorize(Long storeId, MemberDetails member) { - User u = userRepository.findById(member.getId()) - .orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(u.getRole()) && !storeId.equals(u.getStoreId())) { - throw new ReservationViewUnauthorizedException(); + if (reservations.isEmpty()) { + throw new ReservationNotFoundException(); } - return u; - } - // 공통: 오늘 날짜 예약 조회 - private Reservation findTodayReservation(Long storeId, String userId) { - LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); - - return reservationRepository - .findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( - storeId, - Long.valueOf(userId), - List.of(ReservationStatus.WAITING, ReservationStatus.CALLING), - startOfDay, - endOfDay - ) - .orElseThrow(() -> new IllegalArgumentException("오늘 날짜의 예약이 존재하지 않습니다.")); + return reservations.stream() + .map(WaitingUserResponse::fromEntity) + .toList(); } /** @@ -321,5 +296,32 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me throw new IllegalArgumentException("지원하지 않는 상태: " + newStatus); } } + + + /** + * 공통 메서드 + */ + // 오늘 날짜 예약 조회 + private List findTodayWaiting(Long storeId) { + ZoneId zone = ZoneId.of("Asia/Seoul"); + LocalDate today = LocalDate.now(zone); + + return reservationRepository.findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( + storeId, + List.of(ReservationStatus.CONFIRMED, ReservationStatus.CANCELLED), + today.atStartOfDay(zone).toLocalDateTime(), + today.atTime(LocalTime.MAX) + ); + } + + // 사용자 인증 + private User authorize(Long storeId, MemberDetails member) { + User u = userRepository.findById(member.getId()) + .orElseThrow(UserNotFoundException::new); + if (!Role.SUPER_ADMIN.equals(u.getRole()) && !storeId.equals(u.getStoreId())) { + throw new ReservationViewUnauthorizedException(); + } + return u; + } } From 10de21769112da124ea503920ebc3056f68ea4ae Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 11:55:38 +0900 Subject: [PATCH 359/685] =?UTF-8?q?refactor(Reservation):=20Redis=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 96f2e7b9..c5dd1526 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -236,9 +236,6 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus)) { - // Redis 전부 삭제 - waitingRedisRepository.deleteWaiting(storeId, userId); - // 새 Reservation 생성 & 저장 Reservation r = Reservation.builder() .reservationNumber(reservationNumber) @@ -252,6 +249,8 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me // 호출 시각 반영 r.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); Reservation saved = reservationRepository.save(r); + // Redis 전부 삭제 + waitingRedisRepository.deleteWaiting(storeId, userId); return EntryStatusResponseDto.fromEntity(saved); } else { // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 @@ -277,7 +276,6 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me || ReservationStatus.CALLING.name().equals(currStatus))) { throw new IllegalStateException("WAITING/CALLING 상태에서만 취소 가능합니다."); } - waitingRedisRepository.deleteWaiting(storeId, userId); Reservation r = Reservation.builder() .reservationNumber(reservationNumber) @@ -290,6 +288,8 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me r.markUpdated(LocalDateTime.now(), ReservationStatus.CANCELLED); Reservation saved = reservationRepository.save(r); + waitingRedisRepository.deleteWaiting(storeId, userId); + return EntryStatusResponseDto.fromEntity(saved); default: From 6d391fdb844bdbc6a7d76a85e85559b9eccd18a5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 12:19:06 +0900 Subject: [PATCH 360/685] =?UTF-8?q?refactor(Reservation):=20reservationNum?= =?UTF-8?q?ber=20=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WaitingUserRedisRepository.java | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 6ee8b7f4..50c84d83 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -35,7 +35,6 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; - String seqKey = RedisKeyUtils.buildReservationSeqKey(storeId); String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); @@ -44,18 +43,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, if (Boolean.TRUE.equals(added)) { - // 2) 일일 시퀀스: 날짜별로 초기화하려면 - String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // YYYYMMDD - String dailySeqKey = seqKey + ":" + today; // ex. reservation:seq:5:20250728 - - // atomic increment - Long seq = redisTemplate.opsForValue().increment(dailySeqKey, 1); - - // 3) 4자리 0패딩 - String seqStr = String.format("%04d", seq); - - // 4) 최종 ID 조합 - reservationId = storeId + "-" + today + "-" + seqStr; + reservationId = GenerateReservationNumber(seqKey, storeId); // 5) Hash에 저장 redisTemplate.opsForHash().put(numberMapKey, userId, reservationId); @@ -69,6 +57,8 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, redisTemplate.expire(queueKey, ttl); redisTemplate.expire(partyKey, ttl); redisTemplate.expire(statusKey, ttl); + redisTemplate.expire(seqKey, ttl); + redisTemplate.expire(numberMapKey, ttl); } else { Object stored = redisTemplate.opsForHash().get(numberMapKey, userId); reservationId = stored != null ? stored.toString() : null; @@ -193,6 +183,7 @@ public String getReservationId(Long storeId, String userId) { return val != null ? val.toString() : null; } + // 6) TTL 계산: 다음날 03:00 까지 남은 시간 public Duration calculateTTLUntilNext03AM() { // 6-1) Asia/Seoul 기준으로 오늘 자정(내일 00:00) 구하기 ZoneId zone = ZoneId.of("Asia/Seoul"); @@ -204,6 +195,24 @@ public Duration calculateTTLUntilNext03AM() { return Duration.ofSeconds(secondsUntilMidnight); } + + // 예약 번호 생성 + public String GenerateReservationNumber(String seqKey, Long storeId) { + // 2) 일일 시퀀스: 날짜별로 초기화하려면 + String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // YYYYMMDD + String dailySeqKey = seqKey + ":" + today; // ex. reservation:seq:5:20250728 + + // atomic increment + Long seq = redisTemplate.opsForValue().increment(dailySeqKey, 1); + + // 3) 4자리 0패딩 + String seqStr = String.format("%04d", seq); + + // 4) 최종 ID 조합 + String reservationId = storeId + "-" + today + "-" + seqStr; + + return reservationId; + } } From 1a3c06813456585f258be2df6788ff4fb0dbdde2 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:55:52 +0900 Subject: [PATCH 361/685] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8ecd928b..34efd07b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ## 💡프로젝트 소개 주점 대기 예약부터 메뉴 주문까지 원격으로 처리할 수 있는 웨이팅 서비스입니다. +![NoWait-Architecture](https://github.com/user-attachments/assets/408f468c-4ac2-4eb6-93d5-e39c4d5cd557) + ## 📜 프로젝트 목표 From 1775117df97ba91d3b86b22a4afcf5a61315f3d2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 30 Jul 2025 17:50:14 +0900 Subject: [PATCH 362/685] =?UTF-8?q?refactor(discord):=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EB=B0=9C=EC=83=9D=20=EC=8B=9C=EA=B0=84=20=EC=84=9C=EC=9A=B8?= =?UTF-8?q?=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/discord/service/DiscordAlarmService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java b/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java index 3e4e1425..b94499dc 100644 --- a/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java +++ b/nowait-infra/src/main/java/com/nowait/discord/service/DiscordAlarmService.java @@ -3,6 +3,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Arrays; import java.util.List; @@ -54,7 +55,7 @@ private DiscordMessage createMessage(Exception e, WebRequest request) { .embeds(List.of(DiscordMessage.Embed.builder() .title("ℹ️ 에러 정보") .description( - "### 🕖 발생 시간\n" + LocalDateTime.now() + "\n" + "### 🔗 요청 URL\n" + createRequestFullPath(request) + "### 🕖 발생 시간\n" + LocalDateTime.now(ZoneId.of("Asia/Seoul")) + "\n" + "### 🔗 요청 URL\n" + createRequestFullPath(request) + "\n" + "### 📄 Stack Trace\n" + "```\n" + getStackTrace(e).substring(0, 1000) + "\n```") .build())) .build(); From eecd4bf57e8cd20c63535401d23825f0d982e319 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 31 Jul 2025 15:59:41 +0900 Subject: [PATCH 363/685] =?UTF-8?q?refactor(Reservation):=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EB=B9=88=20?= =?UTF-8?q?=EB=B0=B0=EC=97=B4=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index c5dd1526..bb3fe518 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -179,10 +179,6 @@ public List getCompletedWaitingUserDetails(Long storeId, Me authorize(storeId, memberDetails); List reservations = findTodayWaiting(storeId); - if (reservations.isEmpty()) { - throw new ReservationNotFoundException(); - } - return reservations.stream() .map(WaitingUserResponse::fromEntity) .toList(); From 98d33070a7d6cf1dff992a6eca047822bbb53e48 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 31 Jul 2025 16:27:43 +0900 Subject: [PATCH 364/685] =?UTF-8?q?refactor(storePayment):=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20204=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 31 ++++++++++++++----- .../service/StorePaymentService.java | 4 ++- .../service/StorePaymentServiceImpl.java | 12 +++---- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index c784ac1f..ba0020b5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -1,5 +1,7 @@ package com.nowait.applicationuser.storepayment.controller; +import java.util.Optional; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -7,13 +9,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; import com.nowait.applicationuser.storepayment.service.StorePaymentService; import com.nowait.common.api.ApiUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,12 +32,25 @@ public class StorePaymentController { @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") public ResponseEntity getStorePaymentByStoreId(@PathVariable Long storeId) { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - storePaymentService.getStorePaymentByStoreId(storeId) - ) - ); + Optional response = storePaymentService.getStorePaymentByStoreId(storeId); + + if (response.isPresent()) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + ); + } else { + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .body( + ApiUtils.success( + response + ) + ); + } + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java index 4db20e4b..703b8df7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java @@ -1,7 +1,9 @@ package com.nowait.applicationuser.storepayment.service; +import java.util.Optional; + import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; public interface StorePaymentService { - StorePaymentReadDto getStorePaymentByStoreId(Long storeId); + Optional getStorePaymentByStoreId(Long storeId); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java index 7df519f6..5e15b7ff 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java @@ -1,10 +1,10 @@ package com.nowait.applicationuser.storepayment.service; +import java.util.Optional; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; -import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; -import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; import com.nowait.domaincorerdb.storepayment.repository.StorePaymentRepository; @@ -18,12 +18,10 @@ public class StorePaymentServiceImpl implements StorePaymentService { @Override @Transactional(readOnly = true) - public StorePaymentReadDto getStorePaymentByStoreId(Long storeId) { + public Optional getStorePaymentByStoreId(Long storeId) { if (storeId == null) throw new StorePaymentParamEmptyException(); - StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) - .orElseThrow(StoreNotFoundException::new); - - return StorePaymentReadDto.fromEntity(storePayment); + return storePaymentRepository.findByStoreId(storeId) + .map(StorePaymentReadDto::fromEntity); } } From 5e064ba1d5c4b35fe4e1b738ff347b61aa39b247 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 31 Jul 2025 16:31:34 +0900 Subject: [PATCH 365/685] =?UTF-8?q?refactor(storePayment):=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storePayment/exception/StorePaymentNotFoundException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java index af83a4db..f384e132 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java @@ -4,6 +4,6 @@ public class StorePaymentNotFoundException extends RuntimeException { public StorePaymentNotFoundException() { - super(ErrorMessage.STORE_PAYMENT_PARAMETER_EMPTY.getMessage()); + super(ErrorMessage.STORE_PAYMENT_NOT_FOUND.getMessage()); } } From e3fa454a6067b319df3a4835faee9a242aa92308 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 31 Jul 2025 16:43:22 +0900 Subject: [PATCH 366/685] =?UTF-8?q?refactor(storePayment):=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=AA=85=ED=99=95?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 14 ++++++++++---- .../controller/StorePaymentController.java | 5 ++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 919e986d..e332ac26 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -1,10 +1,7 @@ package com.nowait.applicationuser.exception; import static com.nowait.common.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.FORBIDDEN; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.UNAUTHORIZED; import java.util.Map; @@ -35,6 +32,7 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domainuserrdb.bookmark.exception.AlreadyDeletedBookmarkException; @@ -238,6 +236,14 @@ public ErrorResponse handleStoreNotFoundException(StoreNotFoundException e, WebR return new ErrorResponse(e.getMessage(), STORE_NOT_FOUND.getCode()); } + @ResponseStatus(NO_CONTENT) + @ExceptionHandler(StorePaymentNotFoundException.class) + public ErrorResponse handleStorePaymentNotFoundException(StorePaymentNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("handleStorePaymentNotFoundException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_NOT_FOUND.getCode()); + } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index ba0020b5..12901d64 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -44,13 +44,12 @@ public ResponseEntity getStorePaymentByStoreId(@PathVariable Long storeId) { ); } else { return ResponseEntity - .status(HttpStatus.NO_CONTENT) + .status(HttpStatus.OK) .body( ApiUtils.success( - response + "해당 주점의 등록된 결제 정보가 존재하지 않습니다." ) ); } - } } From f3e79a372f554bccb7729822a4b86a3827cad717 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 00:09:02 +0900 Subject: [PATCH 367/685] =?UTF-8?q?refactor(Reservation):=20calling?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=8B=9C=20updatedAt=20=EC=8B=9C=EA=B0=84=20=ED=95=9C=EA=B5=AD?= =?UTF-8?q?=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 5 +++++ .../reservation/service/ReservationService.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index 95fbf33a..dba1ce1e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -31,6 +31,9 @@ public class WaitingUserResponse { @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") private LocalDateTime createdAt; + // @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") + // private LocalDateTime updatedAt; + @Schema(description = "대기 상태", example = "CALLING") private String status; @@ -44,6 +47,7 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { .partySize(reservation.getPartySize()) .userName(reservation.getUser().getNickname()) .createdAt(reservation.getRequestedAt()) + // .updatedAt(reservation.getUpdatedAt()) .status(reservation.getStatus().name()) .build(); } @@ -55,6 +59,7 @@ public static WaitingUserResponse fromRedis(String reservationId, String userId, .partySize(partySize) .userName(userName) .createdAt(createdAt) + // .updatedAt(updatedAt) .status(status) .score(score) .build(); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 3ed9fa8d..a6eb7b2b 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -235,7 +235,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me LocalDateTime requestedAt = score != null ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() : LocalDateTime.now(); - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); switch (newStatus) { case CALLING: From c5de5face964268e1202e8d4b84dfcec13ad1d31 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 00:28:41 +0900 Subject: [PATCH 368/685] =?UTF-8?q?refactor(Reservation):=20calledAt=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 11 +++++----- .../service/ReservationService.java | 20 ++++++++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index dba1ce1e..23d5fa89 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -9,7 +9,6 @@ import lombok.Builder; import lombok.Getter; - @Getter @AllArgsConstructor @Builder @@ -31,8 +30,8 @@ public class WaitingUserResponse { @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") private LocalDateTime createdAt; - // @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") - // private LocalDateTime updatedAt; + @Schema(description = "호출 시각", example = "2025-07-22T16:10:00") + private LocalDateTime calledAt; @Schema(description = "대기 상태", example = "CALLING") private String status; @@ -47,19 +46,19 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { .partySize(reservation.getPartySize()) .userName(reservation.getUser().getNickname()) .createdAt(reservation.getRequestedAt()) - // .updatedAt(reservation.getUpdatedAt()) .status(reservation.getStatus().name()) .build(); } - public static WaitingUserResponse fromRedis(String reservationId, String userId, Integer partySize, String userName, LocalDateTime createdAt, String status, Double score) { + public static WaitingUserResponse fromRedis(String reservationId, String userId, Integer partySize, String userName, + LocalDateTime createdAt, LocalDateTime calledAt, String status, Double score) { return WaitingUserResponse.builder() .reservationNumber(reservationId) .userId(userId) .partySize(partySize) .userName(userName) .createdAt(createdAt) - // .updatedAt(updatedAt) + .calledAt(calledAt) .status(status) .score(score) .build(); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index a6eb7b2b..de1e0474 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -134,6 +134,7 @@ public List getAllWaitingUserDetails(Long storeId) { String pk = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; String sk = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; String nk = RedisKeyUtils.buildReservationNumberKey(storeId); + String cak = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; String qk = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; List pipeline = redisTemplate.executePipelined((RedisCallback)conn -> { @@ -143,6 +144,7 @@ public List getAllWaitingUserDetails(Long storeId) { conn.hGet(pk.getBytes(), uid); // partySize conn.hGet(sk.getBytes(), uid); // status conn.hGet(nk.getBytes(), uid); // reservationId + conn.hGet(cak.getBytes(), uid); // calledAt conn.zScore(qk.getBytes(), uid); // score (등록 시각) } return null; @@ -158,6 +160,7 @@ public List getAllWaitingUserDetails(Long storeId) { Integer partySize = Optional.ofNullable((String)it.next()).map(Integer::valueOf).orElse(0); String status = (String)it.next(); String reservationId = (String)it.next(); + String calledAtStr = (String)it.next(); Double score = (Double)it.next(); // score → createdAt @@ -165,10 +168,15 @@ public List getAllWaitingUserDetails(Long storeId) { ? Instant.ofEpochMilli(score.longValue()).atZone(zone).toLocalDateTime() : null; + LocalDateTime calledAt = calledAtStr != null + ? Instant.ofEpochMilli(Long.parseLong(calledAtStr)).atZone(zone).toLocalDateTime() + : null; + String userName = nicknameMap.getOrDefault(userId, "Unknown"); result.add( - WaitingUserResponse.fromRedis(reservationId, userId, partySize, userName, createdAt, status, score)); + WaitingUserResponse.fromRedis(reservationId, userId, partySize, userName, createdAt, calledAt, status, + score)); } return result; @@ -220,7 +228,8 @@ private Reservation findTodayReservation(Long storeId, String userId) { * - CANCELLED : Redis에서 삭제 → DB 저장 → 취소 메시지 반환 */ @Transactional - public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, ReservationStatus newStatus) { + public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, + ReservationStatus newStatus) { authorize(storeId, member); @@ -243,8 +252,8 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me throw new IllegalStateException("WAITING 상태에서만 CALLING 가능합니다."); } waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); - waitingRedisRepository.setWaitingCalledAt(storeId, userId, now.atZone(ZoneId.of("Asia/Seoul")).toInstant().toEpochMilli()); - + waitingRedisRepository.setWaitingCalledAt(storeId, userId, + now.atZone(ZoneId.of("Asia/Seoul")).toInstant().toEpochMilli()); return EntryStatusResponseDto.builder() .reservationNumber(reservationNumber) @@ -259,7 +268,8 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me case CONFIRMED: // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 - if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus)) { + if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name() + .equals(currStatus)) { // Redis 전부 삭제 waitingRedisRepository.deleteWaiting(storeId, userId); From e77eadf943c10b6b119cfe43c137edec54a657fc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 00:39:09 +0900 Subject: [PATCH 369/685] =?UTF-8?q?refactor(Reservation):=20update=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=20=EC=84=9C=EC=9A=B8=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index de1e0474..02685d78 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -281,7 +281,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .user(userRepository.getReferenceById(Long.valueOf(userId))) .partySize(partySize) .requestedAt(requestedAt) - .updatedAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) .build(); // 호출 시각 반영 @@ -302,7 +302,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me end ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); - existing.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); + existing.markUpdated(LocalDateTime.now(ZoneId.of("Asia/Seoul")), ReservationStatus.CONFIRMED); Reservation saved = reservationRepository.save(existing); return EntryStatusResponseDto.fromEntity(saved); } @@ -320,7 +320,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .user(userRepository.getReferenceById(Long.valueOf(userId))) .partySize(partySize) .requestedAt(requestedAt) - .updatedAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) .build(); r.markUpdated(LocalDateTime.now(), ReservationStatus.CANCELLED); From b611609953584d416f79e58c3ff67a630253f213 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 01:24:35 +0900 Subject: [PATCH 370/685] =?UTF-8?q?refactor(Reservation):=20updatedAt=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index 23d5fa89..2dbbc286 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -33,6 +33,9 @@ public class WaitingUserResponse { @Schema(description = "호출 시각", example = "2025-07-22T16:10:00") private LocalDateTime calledAt; + @Schema(description = "호출 시각", example = "2025-07-22T16:10:00") + private LocalDateTime updatedAt; + @Schema(description = "대기 상태", example = "CALLING") private String status; @@ -46,12 +49,13 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { .partySize(reservation.getPartySize()) .userName(reservation.getUser().getNickname()) .createdAt(reservation.getRequestedAt()) + .updatedAt(reservation.getUpdatedAt()) .status(reservation.getStatus().name()) .build(); } public static WaitingUserResponse fromRedis(String reservationId, String userId, Integer partySize, String userName, - LocalDateTime createdAt, LocalDateTime calledAt, String status, Double score) { + LocalDateTime createdAt, LocalDateTime calledAt,String status, Double score) { return WaitingUserResponse.builder() .reservationNumber(reservationId) .userId(userId) From bf3f280b147b908fca39d536768d1b61a391d95c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 01:27:08 +0900 Subject: [PATCH 371/685] =?UTF-8?q?refactor(storePayment):=20=EC=8B=9C?= =?UTF-8?q?=ED=81=90=EB=A6=AC=ED=8B=B0=20=EA=B6=8C=ED=95=9C=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/config/security/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index fd902f15..56afcbd6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -60,6 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/login/oauth2/code/**", // 카카오 인증 콜백 "/api/refresh-token", // refresh token (토큰 갱신) "/v1/menus/**", // 모든 메뉴 조회 + "/v1/store-payments/**", // 결제 관련 API "/orders/**", "/swagger-ui/**", "/v3/api-docs/**", From 34a43b6bd70321888df6578d25112945fb57ffe3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:40:26 +0900 Subject: [PATCH 372/685] =?UTF-8?q?feat:=20grafana,=20prometheus=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-user-api/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index df5fb1ab..c87e07e1 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -74,6 +74,13 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // actuator + implementation 'org.springframework.boot:spring-boot-starter-actuator' + + // prometheus + implementation 'io.micrometer:micrometer-registry-prometheus' + + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' } From 139d26be593e8d22ffa343d21de21b19e320b5f5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:40:37 +0900 Subject: [PATCH 373/685] =?UTF-8?q?feat:=20grafana,=20prometheus=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy-user-docker.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index aa537c8b..b4cbd711 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -17,8 +17,8 @@ else fi echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -p nowait_dev pull nowait-app-user-api -sudo docker-compose -f docker-compose.user.yml -p nowait_dev up -d nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev pull nowait-app-user-api prometheus grafana +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev up -d nowait-app-user-api prometheus grafana echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From 2853d954f5af1018d4591bbb4ad69f616fb452d7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:40:58 +0900 Subject: [PATCH 374/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 3836cb6b..6b05af82 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -47,7 +47,7 @@ jobs: run: ./gradlew clean :nowait-app-user-api:bootJar -x test - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} @@ -67,28 +67,28 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Copy docker-compose.user.yml to EC2 - uses: appleboy/scp-action@master - with: - host: ${{ secrets.EC2_HOST }} - username: ${{ secrets.EC2_USER }} - key: ${{ secrets.EC2_SSH_KEY }} - port: ${{ secrets.EC2_PORT }} - source: "docker-compose.user.yml" - target: ${{ env.EC2_DEPLOY_PATH }} + - name: Set Prometheus YML + run: | + echo "${{ secrets.PROMETHEUS_DEV_YML }}" \ + | base64 --decode \ + > prometheus.yml - - name: Copy deploy-user-docker to EC2 + - name: Copy compose, prometheus.yml & deploy script to EC2 uses: appleboy/scp-action@master with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "scripts/deploy-user-docker.sh" - target: "${{ env.EC2_DEPLOY_PATH }}" + source: + - "docker-compose.user.yml" + - "docker-compose.user-monitoring.yml" + - "prometheus.yml" + - "scripts/deploy-user-docker.sh" + target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} From 118ebbdacdf28f881d5e53beb354aa8fd4a04505 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:41:17 +0900 Subject: [PATCH 375/685] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=8B=88=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B2=BD=EB=A1=9C=20=EA=B6=8C=ED=95=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/config/security/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 56afcbd6..0fa65142 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -69,7 +69,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/swagger-resources/**", "/webjars/**", "/demo-ui.html", - "/v1/menus//all-menus/stores/**" + "/v1/menus/all-menus/stores/**", + "/nowait/management/**" ) .permitAll() .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 From 63bf808bd6d09d62e10b2b78fa84ed825093624a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:48:23 +0900 Subject: [PATCH 376/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 0c07b13f9d4e598a707688a60a52a2308d6e832f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:51:51 +0900 Subject: [PATCH 377/685] =?UTF-8?q?refactor:=20=EB=A9=80=ED=8B=B0=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 6b05af82..b4ad9724 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -80,11 +80,11 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: - - "docker-compose.user.yml" - - "docker-compose.user-monitoring.yml" - - "prometheus.yml" - - "scripts/deploy-user-docker.sh" + source: | + - docker-compose.user.yml + - docker-compose.user-monitoring.yml + - prometheus.yml + - scripts/deploy-user-docker.sh target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub From ca12deec5504d5bf800da75bd9753d531e592904 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:52:01 +0900 Subject: [PATCH 378/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From cab1fb2c721963e48cb32337f5764bb9dd88852e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:56:30 +0900 Subject: [PATCH 379/685] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index b4ad9724..6e220cdc 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -81,10 +81,10 @@ jobs: key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} source: | - - docker-compose.user.yml - - docker-compose.user-monitoring.yml - - prometheus.yml - - scripts/deploy-user-docker.sh + docker-compose.user.yml + docker-compose.user-monitoring.yml + prometheus.yml + scripts/deploy-user-docker.sh target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub From c7b16cfcf1942fc454ec790f2122a6be9e2c4c4c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 14:56:53 +0900 Subject: [PATCH 380/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 29e48ef8a68c5b1b30ec3403de0f10ebf870295a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:07:13 +0900 Subject: [PATCH 381/685] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 6e220cdc..09354c10 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -80,11 +80,7 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: | - docker-compose.user.yml - docker-compose.user-monitoring.yml - prometheus.yml - scripts/deploy-user-docker.sh + source: "docker-compose.user.yml docker-compose.user-monitoring.yml prometheus.yml scripts/deploy-user-docker.sh" target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub From 0204bc4b8a27fb753c8e9dbfc09b9db80ef8ea37 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:07:17 +0900 Subject: [PATCH 382/685] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..0daa710c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { } } + From 9a2a0d3d7a656f4e977713b40f00d08f870d912d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:11:50 +0900 Subject: [PATCH 383/685] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 09354c10..6e220cdc 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -80,7 +80,11 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "docker-compose.user.yml docker-compose.user-monitoring.yml prometheus.yml scripts/deploy-user-docker.sh" + source: | + docker-compose.user.yml + docker-compose.user-monitoring.yml + prometheus.yml + scripts/deploy-user-docker.sh target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub From 13ac96a315d6e10f780e2fe9cc6f9bf5d34a6f61 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:11:57 +0900 Subject: [PATCH 384/685] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 0daa710c..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,5 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - - From 0add3c39fc26b194806be939ed95e6565156a6a5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:12:52 +0900 Subject: [PATCH 385/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From cf2c368451071c00abfd0eaeb1d5a712d275ed73 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:16:38 +0900 Subject: [PATCH 386/685] =?UTF-8?q?test:=20=EB=94=94=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 6e220cdc..30bf0205 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -67,6 +67,12 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Debug workspace + run: | + echo "현재 작업 디렉터리: $(pwd)" + echo "파일 목록 전체:" + ls -R . + - name: Set Prometheus YML run: | echo "${{ secrets.PROMETHEUS_DEV_YML }}" \ From 116e6f0986997c324e6b16b3ef6bcc447591d32b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:19:52 +0900 Subject: [PATCH 387/685] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EB=A9=94?= =?UTF-8?q?=ED=85=8C=EC=9A=B0=EC=8A=A4=20=EB=8F=84=EC=BB=A4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.user-monitoring.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docker-compose.user-monitoring.yml diff --git a/docker-compose.user-monitoring.yml b/docker-compose.user-monitoring.yml new file mode 100644 index 00000000..282170f9 --- /dev/null +++ b/docker-compose.user-monitoring.yml @@ -0,0 +1,27 @@ +services: + prometheus: + image: prom/prometheus + container_name: prometheus + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + command: + - '--config.file=/etc/prometheus/prometheus.yml' + depends_on: + - nowait-app-user-api + restart: always + + grafana: + image: grafana/grafana + container_name: grafana + ports: + - "3003:3000" + volumes: + - grafana_data:/var/lib/grafana + depends_on: + - prometheus + restart: always + +volumes: + grafana_data: From 3854e2958d5c91ac7a5089f93c9752d0e5867828 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:20:50 +0900 Subject: [PATCH 388/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From caff5a6586d8e70ac14a675a058ed292b2eed0dd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:23:11 +0900 Subject: [PATCH 389/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 1ac14ba4f921f753d2021497784114ef6cdd8e02 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:25:26 +0900 Subject: [PATCH 390/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 30bf0205..a5acdb50 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -67,18 +67,18 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Debug workspace - run: | - echo "현재 작업 디렉터리: $(pwd)" - echo "파일 목록 전체:" - ls -R . - - name: Set Prometheus YML run: | echo "${{ secrets.PROMETHEUS_DEV_YML }}" \ | base64 --decode \ > prometheus.yml + - name: Debug workspace + run: | + echo "현재 작업 디렉터리: $(pwd)" + echo "파일 목록 전체:" + ls -l . + - name: Copy compose, prometheus.yml & deploy script to EC2 uses: appleboy/scp-action@master with: From 3c2f3d6d1cef76b710a0c7aa8c2abf4deb74d6f0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:26:12 +0900 Subject: [PATCH 391/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From bb22dac528171f9b638ce77980370ab0395db7bd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:29:40 +0900 Subject: [PATCH 392/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index a5acdb50..c7524a44 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -86,11 +86,7 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: | - docker-compose.user.yml - docker-compose.user-monitoring.yml - prometheus.yml - scripts/deploy-user-docker.sh + source: "docker-compose.user.yml docker-compose.user-monitoring.yml prometheus.yml scripts/deploy-user-docker.sh" target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub From d2c981be0f3eb760e845d173699c6726ef988e4b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:29:53 +0900 Subject: [PATCH 393/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index c7524a44..fb88e908 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -77,7 +77,7 @@ jobs: run: | echo "현재 작업 디렉터리: $(pwd)" echo "파일 목록 전체:" - ls -l . + ls -l scripts - name: Copy compose, prometheus.yml & deploy script to EC2 uses: appleboy/scp-action@master From b03987d3d1a1da5711c78ea6d5533efc0a3f0701 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:30:35 +0900 Subject: [PATCH 394/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 8da238ba591feca1244d0b729c6cb4e5faf745a9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:34:13 +0900 Subject: [PATCH 395/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index fb88e908..83de2870 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -86,7 +86,11 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "docker-compose.user.yml docker-compose.user-monitoring.yml prometheus.yml scripts/deploy-user-docker.sh" + source: > + docker-compose.user.yml + docker-compose.user-monitoring.yml + prometheus.yml + scripts/deploy-user-docker.sh" target: ${{ env.EC2_DEPLOY_PATH }} - name: Login to DockerHub From 1c5dd07230b386e2252949ba0bce55a32387ba39 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:34:20 +0900 Subject: [PATCH 396/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From 4f4c0c246d983de8049e4498265b687de430a5fd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 15:43:58 +0900 Subject: [PATCH 397/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 39 ++++++++++++++++--- .../java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 83de2870..026d69f7 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -79,20 +79,47 @@ jobs: echo "파일 목록 전체:" ls -l scripts - - name: Copy compose, prometheus.yml & deploy script to EC2 + + - name: Copy docker-compose.user.yml to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "docker-compose.user.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Copy docker-compose.user-monitoring.yml to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "docker-compose.user-monitoring.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Copy prometheus.yml to EC2 uses: appleboy/scp-action@master with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: > - docker-compose.user.yml - docker-compose.user-monitoring.yml - prometheus.yml - scripts/deploy-user-docker.sh" + source: "prometheus.yml" target: ${{ env.EC2_DEPLOY_PATH }} + - name: Copy deploy-user-docker to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "scripts/deploy-user-docker.sh" + target: "${{ env.EC2_DEPLOY_PATH }}" + - name: Login to DockerHub uses: docker/login-action@v2 with: diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 16a16c2b4e70c970492a0f8efa74f6b9b6574299 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 17:30:08 +0900 Subject: [PATCH 398/685] =?UTF-8?q?refactor:=20sh=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy-user-docker.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index b4cbd711..0df38526 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -7,11 +7,19 @@ START_LOG="$REPOSITORY/start.log" echo "1. find container id" CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") +CONTAINER_PROMETHEUS_ID=$(docker ps -q --filter "name=prometheus") +CONTAINER_GRAFANA_ID=$(docker ps -q --filter "name=grafana") echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then echo "Stopping container $CONTAINER_ID" docker rm -f "$CONTAINER_ID" + + echo "Stopping container $CONTAINER_PROMETHEUS_ID" + docker rm -f "$CONTAINER_PROMETHEUS_ID" + + echo "Stopping container $CONTAINER_GRAFANA_ID" + docker rm -f "$CONTAINER_GRAFANA_ID" else echo "No user container found." fi From c2d7ed33c9c86519373c42256799c281b1ca86e0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 1 Aug 2025 17:30:57 +0900 Subject: [PATCH 399/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From 5ae308cf6835e691a65798a7e163886893c8cb82 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 17:49:22 +0900 Subject: [PATCH 400/685] =?UTF-8?q?feat:=20=ED=97=AC=EC=8A=A4=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aws/controller/healthCheckController.java | 28 +++++++++++++++++++ .../aws/controller/healthCheckController.java | 28 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/aws/controller/healthCheckController.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/aws/controller/healthCheckController.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/aws/controller/healthCheckController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/aws/controller/healthCheckController.java new file mode 100644 index 00000000..72438f27 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/aws/controller/healthCheckController.java @@ -0,0 +1,28 @@ +package com.nowait.applicationadmin.aws.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.common.api.ApiUtils; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("") +@RequiredArgsConstructor +public class healthCheckController { + @GetMapping("/health-check") + public ResponseEntity healthCheck() { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils + .success( + "정상 작동 중입니다." + ) + ); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/aws/controller/healthCheckController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/aws/controller/healthCheckController.java new file mode 100644 index 00000000..79ae27c0 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/aws/controller/healthCheckController.java @@ -0,0 +1,28 @@ +package com.nowait.applicationuser.aws.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.common.api.ApiUtils; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("") +@RequiredArgsConstructor +public class healthCheckController { + @GetMapping("/health-check") + public ResponseEntity healthCheck() { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils + .success( + "정상 작동 중입니다." + ) + ); + } +} From 60d3527a11113e81b842e1e55e0087d0e07c8712 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 17:52:04 +0900 Subject: [PATCH 401/685] =?UTF-8?q?refactor:=20=ED=97=AC=EC=8A=A4=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EA=B6=8C=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/config/security/SecurityConfig.java | 3 ++- .../nowait/applicationuser/config/security/SecurityConfig.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java index e7fa71d5..66abaef6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java @@ -54,7 +54,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/api-docs/**", "/swagger-resources/**", "/webjars/**", - "/demo-ui.html" + "/demo-ui.html", + "/health-check" ) .permitAll() .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 0fa65142..97111041 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -70,7 +70,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/webjars/**", "/demo-ui.html", "/v1/menus/all-menus/stores/**", - "/nowait/management/**" + "/nowait/management/**", + "/health-check" ) .permitAll() .anyRequest().authenticated() // 그외 요청은 허가된 사람만 인가 From b650ae8fa8067bd4bfac6202c41c0fc315bc04ec Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 18:29:17 +0900 Subject: [PATCH 402/685] =?UTF-8?q?refactor(storePayment):=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EB=AF=BC=20=EA=B2=B0=EC=A0=9C=EC=88=98=EB=8B=A8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=EA=B0=92=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 29 ++++++++++++++----- .../service/StorePaymentService.java | 4 ++- .../service/StorePaymentServiceImpl.java | 9 +++--- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 7c926dd6..5bcf314d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.storepayment.controller; +import java.util.Optional; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -12,6 +14,7 @@ import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; +import com.nowait.applicationadmin.storepayment.dto.StorePaymentReadDto; import com.nowait.applicationadmin.storepayment.dto.StorePaymentUpdateRequest; import com.nowait.applicationadmin.storepayment.service.StorePaymentService; import com.nowait.common.api.ApiUtils; @@ -52,13 +55,25 @@ public ResponseEntity createStorePayment(@Valid @RequestBody StorePaymentCrea @Operation(summary = "주점 결제 정보 조회", description = "인증된 사용자의 주점 결제 정보를 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") public ResponseEntity getStorePaymentByStoreId(@AuthenticationPrincipal MemberDetails memberDetails) { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - storePaymentService.getStorePaymentByStoreId(memberDetails) - ) - ); + Optional response = storePaymentService.getStorePaymentByStoreId(memberDetails); + + if (response.isPresent()) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + ); + } else { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + "해당 주점의 등록된 결제 정보가 존재하지 않습니다." + ) + ); + } } @PatchMapping("/update") diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java index 4cf3c76f..7dad2fe2 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.storepayment.service; +import java.util.Optional; + import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; import com.nowait.applicationadmin.storepayment.dto.StorePaymentReadDto; @@ -9,6 +11,6 @@ public interface StorePaymentService { StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails); - StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails); + Optional getStorePaymentByStoreId(MemberDetails memberDetails); StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index 67db6c18..a7852824 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.storepayment.service; +import java.util.Optional; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -51,7 +53,7 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r @Override @Transactional(readOnly = true) - public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) { + public Optional getStorePaymentByStoreId(MemberDetails memberDetails) { if (memberDetails == null) throw new StorePaymentParamEmptyException(); User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); @@ -59,10 +61,9 @@ public StorePaymentReadDto getStorePaymentByStoreId(MemberDetails memberDetails) if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new StorePaymentViewUnauthorizedException(); } - StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) - .orElseThrow(StorePaymentNotFoundException::new); - return StorePaymentReadDto.fromEntity(storePayment); + return storePaymentRepository.findByStoreId(storeId) + .map(StorePaymentReadDto::fromEntity); } @Override From 89c6d9464f95885f14b6d0859b44224584e86ffc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 21:44:13 +0900 Subject: [PATCH 403/685] =?UTF-8?q?refactor(APM):=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 9fba398d..b2b40a9c 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -70,6 +70,12 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // actuator + implementation 'org.springframework.boot:spring-boot-starter-actuator' + + // prometheus + implementation 'io.micrometer:micrometer-registry-prometheus' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } From 0c9da0269796019bb1dd0890822a53e55194f3e3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 21:44:46 +0900 Subject: [PATCH 404/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 30 +++++++++++++++++++++-- .github/workflows/deploy-user-docker.yml | 13 +++------- docker-compose.admin-monitoring.yml | 27 ++++++++++++++++++++ docker-compose.user-monitoring.yml | 10 ++++---- scripts/deploy-admin-docker.sh | 12 +++++++-- scripts/deploy-user-docker.sh | 12 ++++----- 6 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 docker-compose.admin-monitoring.yml diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 7eea527b..916204cd 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -48,7 +48,7 @@ jobs: run: ./gradlew clean :nowait-app-admin-api:bootJar -x test - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} @@ -70,6 +70,12 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Set Prometheus YML + run: | + echo "${{ secrets.PROMETHEUS_DEV_ADMIN_YML }}" \ + | base64 --decode \ + > prometheus-admin.yml + - name: Copy docker-compose.admin.yml to EC2 uses: appleboy/scp-action@master with: @@ -80,6 +86,26 @@ jobs: source: "docker-compose.admin.yml" target: ${{ env.EC2_DEPLOY_PATH }} + - name: Copy docker-compose.admin-monitoring.yml to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "docker-compose.admin-monitoring.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + + - name: Copy prometheus-admin.yml to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + port: ${{ secrets.EC2_PORT }} + source: "prometheus-admin.yml" + target: ${{ env.EC2_DEPLOY_PATH }} + - name: Copy deploy-admin-docker.sh to EC2 uses: appleboy/scp-action@master with: @@ -91,7 +117,7 @@ jobs: target: "${{ env.EC2_DEPLOY_PATH }}" - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 026d69f7..3d8e442e 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -69,16 +69,9 @@ jobs: - name: Set Prometheus YML run: | - echo "${{ secrets.PROMETHEUS_DEV_YML }}" \ + echo "${{ secrets.PROMETHEUS_DEV_USER_YML }}" \ | base64 --decode \ - > prometheus.yml - - - name: Debug workspace - run: | - echo "현재 작업 디렉터리: $(pwd)" - echo "파일 목록 전체:" - ls -l scripts - + > prometheus-user.yml - name: Copy docker-compose.user.yml to EC2 uses: appleboy/scp-action@master @@ -107,7 +100,7 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_SSH_KEY }} port: ${{ secrets.EC2_PORT }} - source: "prometheus.yml" + source: "prometheus-user.yml" target: ${{ env.EC2_DEPLOY_PATH }} - name: Copy deploy-user-docker to EC2 diff --git a/docker-compose.admin-monitoring.yml b/docker-compose.admin-monitoring.yml new file mode 100644 index 00000000..3603e161 --- /dev/null +++ b/docker-compose.admin-monitoring.yml @@ -0,0 +1,27 @@ +services: + prometheus: + image: prom/prometheus:latest + container_name: prometheus-admin + ports: + - "9091:9090" + volumes: + - ./prometheus-admin.yml:/etc/prometheus/prometheus.yml:ro + command: + - '--config.file=/etc/prometheus/prometheus.yml' + depends_on: + - nowait-app-admin-api + restart: always + + grafana: + image: grafana/grafana:latest + container_name: grafana-admin + ports: + - "3004:3000" + volumes: + - grafana-admin-data:/var/lib/grafana + depends_on: + - prometheus-admin + restart: always + +volumes: + grafana_data: diff --git a/docker-compose.user-monitoring.yml b/docker-compose.user-monitoring.yml index 282170f9..a3733f69 100644 --- a/docker-compose.user-monitoring.yml +++ b/docker-compose.user-monitoring.yml @@ -1,11 +1,11 @@ services: prometheus: image: prom/prometheus - container_name: prometheus + container_name: prometheus-user ports: - "9090:9090" volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml + - ./prometheus-user.yml:/etc/prometheus/prometheus.yml:ro command: - '--config.file=/etc/prometheus/prometheus.yml' depends_on: @@ -14,13 +14,13 @@ services: grafana: image: grafana/grafana - container_name: grafana + container_name: grafana-user ports: - "3003:3000" volumes: - - grafana_data:/var/lib/grafana + - grafana-user-data:/var/lib/grafana depends_on: - - prometheus + - prometheus-user restart: always volumes: diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index bbb99588..b93b10f7 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -7,18 +7,26 @@ START_LOG="$REPOSITORY/start.log" echo "1. find container id" CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") +CONTAINER_PROMETHEUS_ID=$(docker ps -q --filter "name=prometheus-admin") +CONTAINER_GRAFANA_ID=$(docker ps -q --filter "name=grafana-admin") echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then echo "Stopping container $CONTAINER_ID" docker rm -f "$CONTAINER_ID" + + echo "Stopping container $CONTAINER_PROMETHEUS_ID" + docker rm -f "$CONTAINER_PROMETHEUS_ID" + + echo "Stopping container $CONTAINER_GRAFANA_ID" + docker rm -f "$CONTAINER_GRAFANA_ID" else echo "No admin container found." fi echo "3. start container" -sudo docker-compose -f docker-compose.admin.yml -p nowait_dev pull nowait-app-admin-api -sudo docker-compose -f docker-compose.admin.yml -p nowait_dev up -d nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev pull nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev up -d nowait-app-admin-api prometheus-admin grafana-admin echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 0df38526..9f53cb29 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -7,8 +7,8 @@ START_LOG="$REPOSITORY/start.log" echo "1. find container id" CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") -CONTAINER_PROMETHEUS_ID=$(docker ps -q --filter "name=prometheus") -CONTAINER_GRAFANA_ID=$(docker ps -q --filter "name=grafana") +CONTAINER_PROMETHEUS_ID=$(docker ps -q --filter "name=prometheus-user") +CONTAINER_GRAFANA_ID=$(docker ps -q --filter "name=grafana-user") echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then @@ -18,15 +18,15 @@ if [ -n "$CONTAINER_ID" ]; then echo "Stopping container $CONTAINER_PROMETHEUS_ID" docker rm -f "$CONTAINER_PROMETHEUS_ID" - echo "Stopping container $CONTAINER_GRAFANA_ID" - docker rm -f "$CONTAINER_GRAFANA_ID" + echo "Stopping container $CONTAINER_GRAFANA_ID" + docker rm -f "$CONTAINER_GRAFANA_ID" else echo "No user container found." fi echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev pull nowait-app-user-api prometheus grafana -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev up -d nowait-app-user-api prometheus grafana +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev pull nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev up -d nowait-app-user-api prometheus-user grafana-user echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From 5ed7cfbbd75e3f631a4b6f9f6554897eea5d05e9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:00:43 +0900 Subject: [PATCH 405/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.admin-monitoring.yml | 4 ++-- docker-compose.user-monitoring.yml | 4 ++-- scripts/deploy-admin-docker.sh | 4 ++-- scripts/deploy-user-docker.sh | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose.admin-monitoring.yml b/docker-compose.admin-monitoring.yml index 3603e161..414f1404 100644 --- a/docker-compose.admin-monitoring.yml +++ b/docker-compose.admin-monitoring.yml @@ -20,8 +20,8 @@ services: volumes: - grafana-admin-data:/var/lib/grafana depends_on: - - prometheus-admin + - prometheus restart: always volumes: - grafana_data: + grafana-admin-data: diff --git a/docker-compose.user-monitoring.yml b/docker-compose.user-monitoring.yml index a3733f69..c92dc1e5 100644 --- a/docker-compose.user-monitoring.yml +++ b/docker-compose.user-monitoring.yml @@ -20,8 +20,8 @@ services: volumes: - grafana-user-data:/var/lib/grafana depends_on: - - prometheus-user + - prometheus restart: always volumes: - grafana_data: + grafana-user-data: diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index b93b10f7..6decd539 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -7,8 +7,8 @@ START_LOG="$REPOSITORY/start.log" echo "1. find container id" CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") -CONTAINER_PROMETHEUS_ID=$(docker ps -q --filter "name=prometheus-admin") -CONTAINER_GRAFANA_ID=$(docker ps -q --filter "name=grafana-admin") +CONTAINER_PROMETHEUS_ID=$(docker ps -aq --filter "name=prometheus-admin") +CONTAINER_GRAFANA_ID=$(docker ps -aq --filter "name=grafana-admin") echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 9f53cb29..b4ff5f96 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -7,8 +7,8 @@ START_LOG="$REPOSITORY/start.log" echo "1. find container id" CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") -CONTAINER_PROMETHEUS_ID=$(docker ps -q --filter "name=prometheus-user") -CONTAINER_GRAFANA_ID=$(docker ps -q --filter "name=grafana-user") +CONTAINER_PROMETHEUS_ID=$(docker ps -aq --filter "name=prometheus-user") +CONTAINER_GRAFANA_ID=$(docker ps -aq --filter "name=grafana-user") echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then From 25a374ed4ff4f56ab7fbc16f6fc3e2a3ad4aba49 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:04:05 +0900 Subject: [PATCH 406/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 7ce05119dffd046ae5efff457fcdf868a47a9086 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:10:19 +0900 Subject: [PATCH 407/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.admin-monitoring.yml | 6 +++--- docker-compose.user-monitoring.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.admin-monitoring.yml b/docker-compose.admin-monitoring.yml index 414f1404..4281170d 100644 --- a/docker-compose.admin-monitoring.yml +++ b/docker-compose.admin-monitoring.yml @@ -1,5 +1,5 @@ services: - prometheus: + prometheus-admin: image: prom/prometheus:latest container_name: prometheus-admin ports: @@ -12,7 +12,7 @@ services: - nowait-app-admin-api restart: always - grafana: + grafana-admin: image: grafana/grafana:latest container_name: grafana-admin ports: @@ -20,7 +20,7 @@ services: volumes: - grafana-admin-data:/var/lib/grafana depends_on: - - prometheus + - prometheus-admin restart: always volumes: diff --git a/docker-compose.user-monitoring.yml b/docker-compose.user-monitoring.yml index c92dc1e5..549cdead 100644 --- a/docker-compose.user-monitoring.yml +++ b/docker-compose.user-monitoring.yml @@ -1,5 +1,5 @@ services: - prometheus: + prometheus-user: image: prom/prometheus container_name: prometheus-user ports: @@ -12,7 +12,7 @@ services: - nowait-app-user-api restart: always - grafana: + grafana-user: image: grafana/grafana container_name: grafana-user ports: @@ -20,7 +20,7 @@ services: volumes: - grafana-user-data:/var/lib/grafana depends_on: - - prometheus + - prometheus-user restart: always volumes: From 8532e318bc2bd8d9d3e50e2db2ba49be8462c083 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:10:40 +0900 Subject: [PATCH 408/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From 3ae94cd005e904eaf469e429ab0d5abb7572d435 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:19:39 +0900 Subject: [PATCH 409/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 1 + scripts/deploy-admin-docker.sh | 8 ++++++++ scripts/deploy-user-docker.sh | 12 ++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java index 66abaef6..77febda6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java @@ -55,6 +55,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/swagger-resources/**", "/webjars/**", "/demo-ui.html", + "/nowait/management/**", "/health-check" ) .permitAll() diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 6decd539..e6f93c30 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -14,10 +14,18 @@ echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then echo "Stopping container $CONTAINER_ID" docker rm -f "$CONTAINER_ID" +else + echo "No admin container found." +fi +if [ -n "$CONTAINER_PROMETHEUS_ID" ]; then echo "Stopping container $CONTAINER_PROMETHEUS_ID" docker rm -f "$CONTAINER_PROMETHEUS_ID" +else + echo "No admin container found." +fi +if [ -n "$CONTAINER_GRAFANA_ID" ]; then echo "Stopping container $CONTAINER_GRAFANA_ID" docker rm -f "$CONTAINER_GRAFANA_ID" else diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index b4ff5f96..7ca8c177 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -14,14 +14,22 @@ echo "2. stop container" if [ -n "$CONTAINER_ID" ]; then echo "Stopping container $CONTAINER_ID" docker rm -f "$CONTAINER_ID" +else + echo "No admin container found." +fi +if [ -n "$CONTAINER_PROMETHEUS_ID" ]; then echo "Stopping container $CONTAINER_PROMETHEUS_ID" - docker rm -f "$CONTAINER_PROMETHEUS_ID" + docker rm -f "$CONTAINER_PROMETHEUS_ID" +else + echo "No admin container found." +fi +if [ -n "$CONTAINER_GRAFANA_ID" ]; then echo "Stopping container $CONTAINER_GRAFANA_ID" docker rm -f "$CONTAINER_GRAFANA_ID" else - echo "No user container found." + echo "No admin container found." fi echo "3. start container" From 297608e08d28f246ce5ca8c6aec51e7e9a896df8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:22:30 +0900 Subject: [PATCH 410/685] =?UTF-8?q?refactor(APM):=20prometheus,=20grafana?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,4 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 18614fab8ffd525d70bf06b778503e3ac8cec10b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 22:48:21 +0900 Subject: [PATCH 411/685] =?UTF-8?q?refactor(APM):=20admin=20=EB=8C=80?= =?UTF-8?q?=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EC=A0=91=EA=B7=BC=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/ApiAdminApplication.java | 1 + .../config/security/SecurityConfig.java | 1 + .../main/java/com/nowait/ApiUserApplication.java | 1 - scripts/deploy-admin-docker.sh | 14 -------------- scripts/deploy-user-docker.sh | 14 -------------- 5 files changed, 2 insertions(+), 29 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java index 77febda6..6d1084f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java @@ -55,6 +55,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/swagger-resources/**", "/webjars/**", "/demo-ui.html", + "/nowait/management", "/nowait/management/**", "/health-check" ) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index e6f93c30..0e1dadde 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -18,20 +18,6 @@ else echo "No admin container found." fi -if [ -n "$CONTAINER_PROMETHEUS_ID" ]; then - echo "Stopping container $CONTAINER_PROMETHEUS_ID" - docker rm -f "$CONTAINER_PROMETHEUS_ID" -else - echo "No admin container found." -fi - -if [ -n "$CONTAINER_GRAFANA_ID" ]; then - echo "Stopping container $CONTAINER_GRAFANA_ID" - docker rm -f "$CONTAINER_GRAFANA_ID" -else - echo "No admin container found." -fi - echo "3. start container" sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev pull nowait-app-admin-api prometheus-admin grafana-admin sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev up -d nowait-app-admin-api prometheus-admin grafana-admin diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 7ca8c177..ad31b0f8 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -18,20 +18,6 @@ else echo "No admin container found." fi -if [ -n "$CONTAINER_PROMETHEUS_ID" ]; then - echo "Stopping container $CONTAINER_PROMETHEUS_ID" - docker rm -f "$CONTAINER_PROMETHEUS_ID" -else - echo "No admin container found." -fi - -if [ -n "$CONTAINER_GRAFANA_ID" ]; then - echo "Stopping container $CONTAINER_GRAFANA_ID" - docker rm -f "$CONTAINER_GRAFANA_ID" -else - echo "No admin container found." -fi - echo "3. start container" sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev pull nowait-app-user-api prometheus-user grafana-user sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev up -d nowait-app-user-api prometheus-user grafana-user From 089beb2f26d50741ec7130154bdd38f7bd4a9028 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 3 Aug 2025 23:18:02 +0900 Subject: [PATCH 412/685] =?UTF-8?q?refactor(APM):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EB=A9=94=ED=85=8C=EC=9A=B0=EC=8A=A4=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/config/security/SecurityConfig.java | 1 - .../security/jwt/JwtAuthorizationFilter.java | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java index 6d1084f7..93249ce0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/SecurityConfig.java @@ -78,5 +78,4 @@ public AuthenticationProvider authenticationProvider() { authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } - } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security/jwt/JwtAuthorizationFilter.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security/jwt/JwtAuthorizationFilter.java index a126d366..beb76161 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security/jwt/JwtAuthorizationFilter.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/security/jwt/JwtAuthorizationFilter.java @@ -65,4 +65,10 @@ private String extractTokenFromRequest(HttpServletRequest request) { } return null; } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getRequestURI(); + return path.startsWith("/nowait/management/"); + } } From 93c969b1e607355b0d39922643d3bdf6fd1dd522 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 4 Aug 2025 14:59:24 +0900 Subject: [PATCH 413/685] =?UTF-8?q?refactor(cors):=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=A3=BC=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java index 7cb37fab..4c51b670 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173","http://localhost:63342")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173","http://localhost:63342", "https://no-wait-fe-nowait-admin-2y5y.vercel.app", "https://nowait-admin.co.kr")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From 28f0c1d88033cd73d0567f55092a1f7ef7b27bad Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 4 Aug 2025 20:30:18 +0900 Subject: [PATCH 414/685] =?UTF-8?q?test(Reservation):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-user-api/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index c87e07e1..ab3af0b2 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -82,6 +82,12 @@ dependencies { // test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'com.redis:testcontainers-redis:2.2.4' } test { From 1ed5aabe10636d35d82c4fc9105feafd642c284e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 4 Aug 2025 20:30:35 +0900 Subject: [PATCH 415/685] =?UTF-8?q?test(Reservation):=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=ED=8C=85=20=EA=B8=B0=EB=B3=B8=20=EC=9C=A0=EB=8B=9B=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationServiceTest.java | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java new file mode 100644 index 00000000..36b7deb7 --- /dev/null +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java @@ -0,0 +1,258 @@ +package com.nowait.applicationuser.reservation.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; +import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; +import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; +import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; +import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +@ExtendWith(MockitoExtension.class) +public class ReservationServiceTest { + + @Mock private StoreRepository storeRepository; + @Mock private UserRepository userRepository; + @Mock private WaitingUserRedisRepository waitingRepo; + @Mock private ReservationRepository reservationRepository; + @InjectMocks private ReservationService service; + + + @Test + @DisplayName("registerWaiting: 성공 시 WaitingResponseDto 반환") + void registerWaiting_Success() { + // Given + Long storeId = 10L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(100L); + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(2).build(); + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(true); + User domainUser = mock(User.class); + when(userRepository.findById(100L)).thenReturn(Optional.of(domainUser)); + when(domainUser.getRole()).thenReturn(Role.USER); + when(waitingRepo.addToWaitingQueue(eq(storeId), eq("100"), eq(2), anyLong())) + .thenReturn("10-20250804-0001"); + when(waitingRepo.getRank(storeId, "100")).thenReturn(4L); + + // When + WaitingResponseDto result = service.registerWaiting(storeId, user, dto); + + // Then + assertNotNull(result); + assertEquals("10-20250804-0001", result.getReservationNumber()); + assertEquals(5, result.getRank()); + assertEquals(2, result.getPartySize()); + // Redis 등록 호출 검증 + verify(waitingRepo).addToWaitingQueue(eq(storeId), eq("100"), eq(2), anyLong()); + } + + @Test + @DisplayName("registerWaiting: 스토어 없음 예외") + void registerWaiting_StoreNotFound() { + // Given + Long storeId = 10L; + when(storeRepository.findById(storeId)).thenReturn(Optional.empty()); + + // When/Then + assertThrows(StoreNotFoundException.class, + () -> service.registerWaiting(storeId, mock(CustomOAuth2User.class), null) + ); + } + + @Test + @DisplayName("myWaitingInfo: 정상 조회") + void myWaitingInfo_Success() { + // Given + Long storeId = 5L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(20L); + when(waitingRepo.getRank(storeId, "20")).thenReturn(0L); + when(waitingRepo.getPartySize(storeId, "20")).thenReturn(3); + when(waitingRepo.getReservationId(storeId, "20")).thenReturn("5-20250804-0001"); + + // When + WaitingResponseDto info = service.myWaitingInfo(storeId, user); + + // Then + assertNotNull(info); + assertEquals("5-20250804-0001", info.getReservationNumber()); + assertEquals(1, info.getRank()); + assertEquals(3, info.getPartySize()); + } + + @Test + @DisplayName("cancelWaiting: 성공 시 true 반환 및 DB 저장 호출") + void cancelWaiting_Success() { + // Given + Long storeId = 7L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(30L); + when(waitingRepo.getReservationId(storeId, "30")).thenReturn("RID-1"); + when(waitingRepo.getPartySize(storeId, "30")).thenReturn(4); + when(waitingRepo.getWaitingTimestamp(storeId, "30")).thenReturn(Instant.now().toEpochMilli()); + when(waitingRepo.removeWaiting(storeId, "30")).thenReturn(true); + + // When + boolean removed = service.cancelWaiting(storeId, user); + + // Then + assertTrue(removed); + verify(reservationRepository).save(any(Reservation.class)); + } + + @Test + @DisplayName("getAllMyWaitings: 대기 없음 시 빈 리스트 반환") + void getAllMyWaitings_Empty() { + // Given + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(40L); + when(waitingRepo.getUserWaitingStoreIds("40")).thenReturn(Collections.emptyList()); + + // When + List list = service.getAllMyWaitings(user); + + // Then + assertTrue(list.isEmpty()); + } + + + @Test + @DisplayName("create(DB): 성공 시 DTO 반환") + void create_Success() { + // Given + Long storeId = 2L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(50L); + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(5).build(); + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(true); + User domainUser = mock(User.class); + when(userRepository.findById(50L)).thenReturn(Optional.of(domainUser)); + when(reservationRepository.existsByUserAndStoreAndStatusIn(eq(domainUser), eq(store), anyList())) + .thenReturn(false); + Reservation saved = Reservation.builder() + .id(99L) + .store(store) + .user(domainUser) + .requestedAt(LocalDateTime.now()) + .status(ReservationStatus.WAITING) + .partySize(5) + .build(); + when(reservationRepository.save(any(Reservation.class))).thenReturn(saved); + + // When + ReservationCreateResponseDto res = service.create(storeId, user, dto); + + // Then + assertNotNull(res); + assertEquals(99L, res.getId()); + assertEquals(5, res.getPartySize()); + } + + @Test + @DisplayName("create(DB): 중복 예약 예외") + void create_DuplicateException() { + // Given + Long storeId = 2L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(50L); + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(1).build(); + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(true); + User domainUser = mock(User.class); + when(userRepository.findById(50L)).thenReturn(Optional.of(domainUser)); + when(reservationRepository.existsByUserAndStoreAndStatusIn(eq(domainUser), eq(store), anyList())) + .thenReturn(true); + + // When/Then + assertThrows(DuplicateReservationException.class, + () -> service.create(storeId, user, dto) + ); + } + + @Test + @DisplayName("create(DB): 사용자 없음 예외") + void create_UserNotFound() { + // Given + Long storeId = 2L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(50L); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(mock(Store.class))); + when(userRepository.findById(50L)).thenReturn(Optional.empty()); + + // When/Then + assertThrows(UserNotFoundException.class, + () -> service.create(storeId, user, ReservationCreateRequestDto.builder().partySize(1).build()) + ); + } + + @Test + @DisplayName("create(DB): 스토어 없음 예외") + void create_StoreNotFound() { + // Given + Long storeId = 3L; + when(storeRepository.findById(storeId)).thenReturn(Optional.empty()); + + // When/Then + assertThrows(StoreNotFoundException.class, + () -> service.create(storeId, mock(CustomOAuth2User.class), ReservationCreateRequestDto.builder().partySize(1).build()) + ); + } + + @Test + @DisplayName("create(DB): 스토어 비활성화 예외") + void create_StoreDisabledException() { + // Given + Long storeId = 4L; + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(60L); + + // 1) 사용자 조회 스텁 추가 + User domainUser = mock(User.class); + when(userRepository.findById(60L)).thenReturn(Optional.of(domainUser)); + + // 2) 스토어 조회 및 비활성화 스텁 + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(false); + + // When / Then + assertThrows(StoreWaitingDisabledException.class, + () -> service.create( + storeId, + user, + ReservationCreateRequestDto.builder().partySize(2).build() + ) + ); + } +} From 484a79b244e3c3a2501d8a7740168d29b1cf1ad3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 4 Aug 2025 20:30:43 +0900 Subject: [PATCH 416/685] =?UTF-8?q?test(Reservation):=20=EB=A9=80=ED=8B=B0?= =?UTF-8?q?=20=EC=8A=A4=EB=A0=88=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationServiceConcurrencyTest.java | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java new file mode 100644 index 00000000..fd7b3614 --- /dev/null +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java @@ -0,0 +1,176 @@ +package com.nowait.applicationuser.reservation.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; +import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; +import com.nowait.common.enums.Role; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.repository.StoreImageRepository; +import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; +import com.redis.testcontainers.RedisContainer; + +@Testcontainers +class ReservationServiceConcurrencyTest { + + @Container + static RedisContainer redis = new RedisContainer("redis:6.2.6"); + + static StringRedisTemplate redisTemplate; + static WaitingUserRedisRepository waitingRepo; + static ReservationService reservationService; + + // Mockito로 대체할 의존성들 + static ReservationRepository reservationRepo; + static StoreRepository storeRepo; + static UserRepository userRepo; + static DepartmentRepository deptRepo; + static StoreImageRepository storeImageRepo; + + private static final Long STORE_ID = 100L; + private static final int THREAD_COUNT = 50; + + @BeforeAll + static void setupAll() { + // 1) RedisTemplate 초기화 + LettuceConnectionFactory factory = new LettuceConnectionFactory( + redis.getHost(), + redis.getFirstMappedPort() + ); + factory.afterPropertiesSet(); + redisTemplate = new StringRedisTemplate(factory); + + // 2) 실제 Redis 리포지토리 객체 생성 + waitingRepo = new WaitingUserRedisRepository(redisTemplate); + + // 3) Mockito mock 인스턴스 생성 + reservationRepo = mock(ReservationRepository.class); + storeRepo = mock(StoreRepository.class); + userRepo = mock(UserRepository.class); + deptRepo = mock(DepartmentRepository.class); + storeImageRepo = mock(StoreImageRepository.class); + + // 4) store/user 유효성 검증 스텁 + Store mockStore = mock(Store.class); + when(mockStore.getIsActive()).thenReturn(true); + when(storeRepo.findById(anyLong())).thenReturn(Optional.of(mockStore)); + + User mockUser = mock(User.class); + when(mockUser.getRole()).thenReturn(Role.USER); + when(userRepo.findById(anyLong())).thenReturn(Optional.of(mockUser)); + + // 5) ReservationService 실체 생성 + reservationService = new ReservationService( + reservationRepo, + storeRepo, + userRepo, + waitingRepo, + deptRepo, + storeImageRepo, + redisTemplate + ); + } + + @BeforeEach + void clearRedis() { + // 매 테스트마다 Redis 키 초기화 + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*")); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + "*")); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingStatusKeyPrefix() + "*")); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationNumberKey(STORE_ID) + ":*")); + } + + @Test + @DisplayName("동시 50명 대기 등록 테스트 (Given–When–Then)") + void concurrentRegisterWaiting() throws InterruptedException { + // --- Given --- + // 50개 스레드를 준비하고, 동시에 시작/종료를 제어할 CountDownLatch + ExecutorService exec = Executors.newFixedThreadPool(THREAD_COUNT); + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch finishLatch = new CountDownLatch(THREAD_COUNT); + // 결과를 수집할 스레드 안전 리스트 + List responses = Collections.synchronizedList(new ArrayList<>()); + + // THREAD_COUNT개의 작업을 스레드풀에 제출 + for (int i = 0; i < THREAD_COUNT; i++) { + final long uid = 1000 + i; + exec.submit(() -> { + try { + // 모든 스레드가 startLatch.await() 대기 중 + startLatch.await(); + + // 각 스레드마다 OAuth2User mocking + CustomOAuth2User user = mock(CustomOAuth2User.class); + when(user.getUserId()).thenReturn(uid); + + // 실제 호출 + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder() + .partySize(2) + .build(); + WaitingResponseDto responseDto = reservationService.registerWaiting(STORE_ID, user, dto); + responses.add(responseDto); + + } catch (Exception e) { + fail("예외 발생: " + e.getMessage()); + } finally { + // 작업 완료 신고 + finishLatch.countDown(); + } + }); + } + + // --- When --- + // 모든 스레드를 동시에 시작 + startLatch.countDown(); + // 최대 15초 대기 + boolean completedInTime = finishLatch.await(15, TimeUnit.SECONDS); + + // --- Then --- + // 1) 모든 스레드가 제시간에 완료되었는가? + assertTrue(completedInTime, "스레드가 제시간에 완료되지 않았습니다."); + // 2) 반환된 DTO 개수가 50개인가? + assertEquals(THREAD_COUNT, responses.size(), "전체 응답 수 불일치"); + + // 3) 예약번호가 모두 유니크한가? + Set reservationIds = new HashSet<>(); + for (WaitingResponseDto r : responses) { + assertNotNull(r.getReservationNumber(), "예약번호가 null 입니다"); + reservationIds.add(r.getReservationNumber()); + } + assertEquals(THREAD_COUNT, reservationIds.size(), "예약번호가 중복되었습니다"); + + // 스레드풀 정리 + exec.shutdown(); + } + +} From 60c65fd19ee6901774a65ac0a985d5637ff57c55 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 12:28:07 +0900 Subject: [PATCH 417/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20cors=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index 97b483c9..437ac896 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342", "https://nowait.co.kr", "https://www.nowait.co.kr", "https://nowait-user.vercel.app")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From f164ddcb0e96f4784ef8887e3ab91786046b9f5f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 12:34:11 +0900 Subject: [PATCH 418/685] =?UTF-8?q?refactor(discord):=20=EC=9B=B9=ED=9B=85?= =?UTF-8?q?=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/discord/client/DiscordClientAdmin.java | 2 +- .../main/java/com/nowait/discord/client/DiscordClientUser.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java index 7d9adabd..3cde6222 100644 --- a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java +++ b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java @@ -10,7 +10,7 @@ @FeignClient( name = "discordClientAdmin", contextId = "discordClientAdmin", - url = "https://discord.com/api/webhooks/1399070629994565653/Cy1_421v3-rN7U_7mKO7tcbwnGxS6Ufmf-T-uorAp8Bn0EoqDlrnKKvaf91PftxD1fRi", + url = "${discord.webhook.admin-url}", configuration = DiscordFeignConfiguration.class) public interface DiscordClientAdmin { @PostMapping() diff --git a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java index a295e740..79238c4c 100644 --- a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java +++ b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientUser.java @@ -10,7 +10,7 @@ @FeignClient( name = "discordClientUser", contextId = "discordClientUser", - url = "https://discord.com/api/webhooks/1399081071127171203/dvop__zPnQKCX-1VMJyBYnU_KTFNJFUwgGwta9D1Zy0xON0hKDcTW2ke9TQfnywzD2ll", + url = "${discord.webhook.user-url}", configuration = DiscordFeignConfiguration.class) public interface DiscordClientUser { @PostMapping() From dcb87c4b5a27165e411b0eb9a2c9af08873b6151 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 13:11:04 +0900 Subject: [PATCH 419/685] =?UTF-8?q?refactor(discord):=20=EC=9B=B9=ED=9B=85?= =?UTF-8?q?=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/discord/client/DiscordClientAdmin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java index 3cde6222..c7504eb0 100644 --- a/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java +++ b/nowait-infra/src/main/java/com/nowait/discord/client/DiscordClientAdmin.java @@ -8,8 +8,8 @@ import com.nowait.discord.dto.DiscordMessage; @FeignClient( - name = "discordClientAdmin", - contextId = "discordClientAdmin", + name = "discordClientAdmin", + contextId = "discordClientAdmin", url = "${discord.webhook.admin-url}", configuration = DiscordFeignConfiguration.class) public interface DiscordClientAdmin { From 0cc63ec311cedafa9d87cc9f19e2216ede121da4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 18:22:56 +0900 Subject: [PATCH 420/685] =?UTF-8?q?test:=20=EC=9E=84=EC=8B=9C=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,4 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From da0200fbd3f810bc96f51fe2fd603ede33b9e11a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 19:13:34 +0900 Subject: [PATCH 421/685] =?UTF-8?q?bug:=20cGroup=20v1=20=EB=A7=88=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=A4=ED=8C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/Dockerfile | 2 +- nowait-app-user-api/Dockerfile | 2 +- nowait-common/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index 8b17c261..01e8a5f1 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM openjdk:17.0.8-jdk ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-admin-api.jar diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index 8daac893..ebdad08f 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM openjdk:17.0.8-jdk ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-user-api.jar diff --git a/nowait-common/Dockerfile b/nowait-common/Dockerfile index 9c4d8549..a07d813d 100644 --- a/nowait-common/Dockerfile +++ b/nowait-common/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM openjdk:17.0.8-jdk WORKDIR /app COPY build/libs/*.jar nowait-common.jar From a2356c8ad3336313ce195d2293b55bee14e63dfb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 19:28:17 +0900 Subject: [PATCH 422/685] =?UTF-8?q?bug:=20cGroup=20v1=20=EB=A7=88=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=A4=ED=8C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/Dockerfile | 2 +- nowait-app-user-api/Dockerfile | 2 +- nowait-common/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index 01e8a5f1..8b17c261 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17.0.8-jdk +FROM openjdk:17 ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-admin-api.jar diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index ebdad08f..8daac893 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17.0.8-jdk +FROM openjdk:17 ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-user-api.jar diff --git a/nowait-common/Dockerfile b/nowait-common/Dockerfile index a07d813d..9c4d8549 100644 --- a/nowait-common/Dockerfile +++ b/nowait-common/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17.0.8-jdk +FROM openjdk:17 WORKDIR /app COPY build/libs/*.jar nowait-common.jar From ed005da55334df100c61eeb30a1b4bdd06b745e7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 19:49:00 +0900 Subject: [PATCH 423/685] =?UTF-8?q?bug:=20cGroup=20v1=20=EB=A7=88=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=A4=ED=8C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/Dockerfile | 2 +- nowait-app-user-api/Dockerfile | 2 +- nowait-common/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index 8b17c261..6ace55d1 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM openjdk:17-slim ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-admin-api.jar diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index 8daac893..2cd8677f 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM openjdk:17-slim ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-user-api.jar diff --git a/nowait-common/Dockerfile b/nowait-common/Dockerfile index 9c4d8549..62b4fcea 100644 --- a/nowait-common/Dockerfile +++ b/nowait-common/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM openjdk:17-slim WORKDIR /app COPY build/libs/*.jar nowait-common.jar From a8317b0577801e3968f9099c81c617e14f576ff2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 20:17:15 +0900 Subject: [PATCH 424/685] =?UTF-8?q?bug:=20cGroup=20v1=20=EB=A7=88=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=A4=ED=8C=A8=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=EC=9E=90=EB=B0=94=20=EB=B2=84=EC=A0=84=20=EC=97=85=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/Dockerfile | 2 +- nowait-app-user-api/Dockerfile | 2 +- nowait-common/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index 6ace55d1..457d3fd7 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-slim +FROM openjdk:21-slim ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-admin-api.jar diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index 2cd8677f..0c122c2f 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-slim +FROM openjdk:21-slim ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-user-api.jar diff --git a/nowait-common/Dockerfile b/nowait-common/Dockerfile index 62b4fcea..ce3e2380 100644 --- a/nowait-common/Dockerfile +++ b/nowait-common/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-slim +FROM openjdk:21-slim WORKDIR /app COPY build/libs/*.jar nowait-common.jar From 942114b94c48a9c5ca008876a733836dc626c0fe Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Tue, 5 Aug 2025 21:09:35 +0900 Subject: [PATCH 425/685] =?UTF-8?q?refactor(order):=20=EC=A3=BC=EB=AC=B8?= =?UTF-8?q?=20=EB=82=B4=EC=97=AD=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 메뉴 가격 추가 --- .../order/dto/MenuDetail.java | 11 +++++++ .../order/dto/OrderResponseDto.java | 31 +++++++++++++------ 2 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/MenuDetail.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/MenuDetail.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/MenuDetail.java new file mode 100644 index 00000000..cb99d483 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/MenuDetail.java @@ -0,0 +1,11 @@ +package com.nowait.applicationadmin.order.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MenuDetail { + private int quantity; + private int price; +} diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java index 232413ee..ccca3fd6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -1,9 +1,8 @@ package com.nowait.applicationadmin.order.dto; import java.time.LocalDateTime; -import java.util.List; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.stream.Collectors; import com.nowait.domaincorerdb.order.entity.OrderItem; import com.nowait.domaincorerdb.order.entity.OrderStatus; @@ -20,16 +19,27 @@ public class OrderResponseDto { private String depositorName; private Integer totalPrice; private OrderStatus status; - private Map menuNamesAndQuantities; + private Map menuDetails; private LocalDateTime createdAt; public static OrderResponseDto fromEntity(UserOrder userOrder) { - Map menuNamesAndQuantities = userOrder.getOrderItems().stream() - .collect(Collectors.toMap( - orderItem -> orderItem.getMenu().getName(), - OrderItem::getQuantity, - Integer::sum // 메뉴명이 중복일 때 수량 합침 - )); + Map menuDetails = new LinkedHashMap<>(); + + for (OrderItem item : userOrder.getOrderItems()) { + String name = item.getMenu().getName(); + int quantity = item.getQuantity(); + int price = item.getMenu().getPrice(); // 메뉴 단가 + + // 메뉴명이 중복되면 수량만 누적 + // merge 람다함수 활용(해당 key가 있으면 수량 누적, 없으면 새로 생성) + menuDetails.merge(name, + new MenuDetail(quantity, price), + (oldVal, newVal) -> new MenuDetail( + oldVal.getQuantity() + newVal.getQuantity(), + oldVal.getPrice() // 단가는 동일하다는 가정 + ) + ); + } return OrderResponseDto.builder() .id(userOrder.getId()) @@ -37,8 +47,9 @@ public static OrderResponseDto fromEntity(UserOrder userOrder) { .depositorName(userOrder.getDepositorName()) .totalPrice(userOrder.getTotalPrice()) .status(userOrder.getStatus()) - .menuNamesAndQuantities(menuNamesAndQuantities) + .menuDetails(menuDetails) .createdAt(userOrder.getCreatedAt()) .build(); } } + From 3522a0926076a2ce6cf8db5e26605c3750fecf95 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 5 Aug 2025 23:42:13 +0900 Subject: [PATCH 426/685] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=B0=ED=8F=AC=EC=A3=BC=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index 97b483c9..f70beb4c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342", "https://nowait-user.vercel.app", "https://nowait.co.kr", "https://www.nowait.co.kr")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From a07d0554574f1c0db88bf108e97a9d223c20692f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 16:12:27 +0900 Subject: [PATCH 427/685] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=B0=ED=8F=AC=EC=A3=BC=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index f70beb4c..b89e7b7c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342", "https://nowait-user.vercel.app", "https://nowait.co.kr", "https://www.nowait.co.kr")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342", "https://nowait-user.vercel.app", "https://nowait.co.kr")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From 11053b5ba7791e10e12526ddd71828983ea02d91 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 16:24:28 +0900 Subject: [PATCH 428/685] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EA=B0=84=EA=B2=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/repository/StatisticCustomRepository.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java index fcdeeaf7..4bb96013 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java @@ -9,8 +9,6 @@ import com.nowait.domainadminrdb.statistic.dto.StoreSales; import com.nowait.domainadminrdb.statistic.dto.TopSalesStoresDetail; - - public interface StatisticCustomRepository { OrderSalesSumDetail findSalesSumByStoreId(Long storeId, LocalDate date); @@ -19,7 +17,6 @@ public interface StatisticCustomRepository { Map findOrderCountByStoreIds(List storeIds); - // redis 사용하는 부분 List findTotalSales(); From 3ef6ea58cba752a7ff4a7f74fcb7fb8069c387d7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 16:24:59 +0900 Subject: [PATCH 429/685] =?UTF-8?q?refactor(Order):=20=EB=8B=B9=EC=9D=BC?= =?UTF-8?q?=20=EC=A3=BC=EB=AC=B8=EB=A7=8C=20=EC=A1=B0=ED=9A=8C=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 2 +- .../applicationadmin/order/service/OrderService.java | 8 +++++++- .../domaincorerdb/order/repository/OrderRepository.java | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 7022486b..a45365ce 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -35,7 +35,7 @@ public class OrderController { @GetMapping("/{storeId}") @Operation(summary = "주점별 주문리스트 조회", description = "특정 주점에 대한 예약리스트 조회") - @ApiResponse(responseCode = "200", description = "주리스트 조회") + @ApiResponse(responseCode = "200", description = "주문 리스트 조회") public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId, @AuthenticationPrincipal MemberDetails memberDetails) { List response = orderService.findAllOrders(storeId, memberDetails); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 105afbfe..dfc88532 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -1,6 +1,8 @@ package com.nowait.applicationadmin.order.service; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import java.util.stream.Collectors; @@ -46,7 +48,11 @@ public List findAllOrders(Long storeId, MemberDetails memberDe if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { throw new OrderViewUnauthorizedException(); } - return orderRepository.findAllByStore_StoreId(storeId) + + LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); + LocalDateTime startDateTime = today.atStartOfDay(); + LocalDateTime endDateTime = today.plusDays(1).atStartOfDay(); + return orderRepository.findAllByStore_StoreIdAndCreatedAtBetween(storeId, startDateTime, endDateTime) .stream() .map(OrderResponseDto::fromEntity) .collect(Collectors.toList()); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index 01605efc..b1a38e8e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -16,5 +16,5 @@ public interface OrderRepository extends JpaRepository { List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); @EntityGraph(attributePaths = {"orderItems", "orderItems.menu"}) - List findAllByStore_StoreId(Long storeId); + List findAllByStore_StoreIdAndCreatedAtBetween(Long storeId, LocalDateTime startDateTime, LocalDateTime endDateTime); } From 5534d1b5329498d3f1806df0770ef06ee2b17217 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 16:43:49 +0900 Subject: [PATCH 430/685] =?UTF-8?q?refactor(Order):=20adminDisplayName=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/domaincorerdb/menu/entity/Menu.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java index 0c7fdae8..6866af43 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java @@ -31,6 +31,9 @@ public class Menu extends BaseTimeEntity { @Column(nullable = false) private Long storeId; + @Column(nullable = false) + private String adminDisplayName; + @Column(nullable = false) private String name; @@ -47,10 +50,11 @@ public class Menu extends BaseTimeEntity { private Boolean deleted; - public Menu(LocalDateTime createdAt, Long id, Long storeId, String name, String description, Integer price, Boolean isSoldOut, Boolean deleted) { + public Menu(LocalDateTime createdAt, Long id, Long storeId, String adminDisplayName, String name, String description, Integer price, Boolean isSoldOut, Boolean deleted) { super(createdAt); this.Id = id; this.storeId = storeId; + this.adminDisplayName = adminDisplayName; this.name = name; this.description = description; this.price = price; @@ -58,7 +62,8 @@ public Menu(LocalDateTime createdAt, Long id, Long storeId, String name, String this.deleted = deleted != null ? deleted : false; } - public void updateInfo(String name, String description, Integer price) { + public void updateInfo(String adminDisplayName, String name, String description, Integer price) { + if (adminDisplayName != null) this.adminDisplayName = adminDisplayName; if (name != null) this.name = name; if (description != null) this.description = description; if (price != null) this.price = price; From e22d45277b1b7e84ce6982806ecd6586911b0586 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 16:44:00 +0900 Subject: [PATCH 431/685] =?UTF-8?q?refactor(Order):=20adminDisplayName=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationadmin/menu/dto/MenuCreateRequest.java | 3 +++ .../nowait/applicationadmin/menu/dto/MenuCreateResponse.java | 3 +++ .../java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java | 3 +++ .../nowait/applicationadmin/menu/dto/MenuUpdateRequest.java | 1 + .../com/nowait/applicationadmin/menu/service/MenuService.java | 1 + 5 files changed, 11 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java index 0a303176..c422234f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java @@ -16,6 +16,8 @@ public class MenuCreateRequest { @NotNull private Long storeId; @NotNull + private String adminDisplayName; + @NotNull private String name; @NotNull private String description; @@ -25,6 +27,7 @@ public class MenuCreateRequest { public Menu toEntity() { return Menu.builder() .storeId(storeId) + .adminDisplayName(adminDisplayName != null ? adminDisplayName : name) .name(name) .description(description) .price(price) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java index bef0ce8d..cec62e1d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java @@ -4,6 +4,7 @@ import com.nowait.domaincorerdb.menu.entity.Menu; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -14,6 +15,7 @@ public class MenuCreateResponse { private Long menuId; private Long storeId; + private String adminDisplayName; private String name; private String description; private Integer price; @@ -26,6 +28,7 @@ public static MenuCreateResponse fromEntity(Menu menu) { .createdAt(menu.getCreatedAt()) .menuId(menu.getId()) .storeId(menu.getStoreId()) + .adminDisplayName(menu.getAdminDisplayName()) .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java index 53fffd71..a5e25126 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java @@ -4,6 +4,7 @@ import com.nowait.domaincorerdb.menu.entity.Menu; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -14,6 +15,7 @@ public class MenuReadDto { private Long menuId; private Long storeId; + private String adminDisplayName; private String name; private String description; private Integer price; @@ -25,6 +27,7 @@ public static MenuReadDto fromEntity(Menu menu, List im return MenuReadDto.builder() .menuId(menu.getId()) .storeId(menu.getStoreId()) + .adminDisplayName(menu.getAdminDisplayName()) .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java index 3f2878aa..cf404747 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuUpdateRequest.java @@ -10,6 +10,7 @@ @NoArgsConstructor @Builder public class MenuUpdateRequest { + private String adminDisplayName; private String name; private String description; private Integer price; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 5c31d45e..54972594 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -107,6 +107,7 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDeta } menu.updateInfo( + request.getAdminDisplayName(), request.getName(), request.getDescription(), request.getPrice() From 56b981e394a2d9cedbd61af09a4a75dc4087ee78 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 16:56:16 +0900 Subject: [PATCH 432/685] =?UTF-8?q?refactor(Menu):=20nullable=20true?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/domaincorerdb/menu/entity/Menu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java index 6866af43..1ea3a632 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java @@ -31,7 +31,7 @@ public class Menu extends BaseTimeEntity { @Column(nullable = false) private Long storeId; - @Column(nullable = false) + @Column(nullable = true) private String adminDisplayName; @Column(nullable = false) From 29413a2daf224d7c071de4c629e134fe61c81991 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 17:18:21 +0900 Subject: [PATCH 433/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index f583cc28..59a82bf1 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -70,7 +70,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) - String targetUrl = "http://localhost:5173/login/success?accessToken=" + accessToken; + String targetUrl = "http://43.202.201.254:8081/login/success?accessToken=" + accessToken; response.sendRedirect(targetUrl); } From 9d8ef0b60818eee0ee824c11812df05e44ac4dfa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 17:43:05 +0900 Subject: [PATCH 434/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 59a82bf1..7b787586 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -73,5 +73,4 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String targetUrl = "http://43.202.201.254:8081/login/success?accessToken=" + accessToken; response.sendRedirect(targetUrl); } - } From 29c8b84cbdabb961380f108e56cc2a69817422a1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 17:55:30 +0900 Subject: [PATCH 435/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 7b787586..4a843688 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -70,7 +70,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) - String targetUrl = "http://43.202.201.254:8081/login/success?accessToken=" + accessToken; + String targetUrl = "https://nowait-user.vercel.app/login/success?accessToken=" + accessToken; response.sendRedirect(targetUrl); } } From 26db27110955583c2dbb5685e228584aeed65a97 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 19:37:56 +0900 Subject: [PATCH 436/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.user.yml | 4 ++-- .../nowait/applicationadmin/menu/dto/MenuCreateRequest.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docker-compose.user.yml b/docker-compose.user.yml index 0e14807a..dbcd1360 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -19,8 +19,8 @@ services: - "8081:8081" depends_on: - mysql - volumes: - - ./nowait-app-user-api:/app +# volumes: +# - ./nowait-app-user-api:/app restart: always volumes: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java index c422234f..4b5fc37c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java @@ -15,7 +15,6 @@ public class MenuCreateRequest { @NotNull private Long storeId; - @NotNull private String adminDisplayName; @NotNull private String name; From 4cb7425970617f8176422de48307e5a3118dc13c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 19:38:33 +0900 Subject: [PATCH 437/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From f5f8df2e3811ce72174c9ba32cc84527961e6202 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 21:19:49 +0900 Subject: [PATCH 438/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EB=B0=B0=ED=8F=AC=20=ED=83=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 ++++-- docker-compose.user.yml | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 3d8e442e..9a68b401 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -55,8 +55,10 @@ jobs: - name: Build and Push Docker Image run: | - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:user ./nowait-app-user-api - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:user + TAG=${GITHUB_SHA::7} + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest ./nowait-app-user-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest deploy: name: Deploy to EC2 diff --git a/docker-compose.user.yml b/docker-compose.user.yml index dbcd1360..3c08e4a4 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -13,14 +13,14 @@ services: - mysql_data:/var/lib/mysql nowait-app-user-api: - image: jihuneddie/nowait_dev:user + image: jihuneddie/nowait_dev:latest container_name: nowait-app-user-api ports: - "8081:8081" depends_on: - mysql -# volumes: -# - ./nowait-app-user-api:/app + volumes: + - ./nowait-app-user-api:/app restart: always volumes: From 3088c0f2d0cedfea24a9763ee6fdd9e5adc9c49b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 21:20:54 +0900 Subject: [PATCH 439/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EB=B0=B0=ED=8F=AC=20=ED=83=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 665651649638131d083d44664a3dfd230d03fd37 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 21:35:56 +0900 Subject: [PATCH 440/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EB=B0=B0=ED=8F=AC=20=ED=83=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From f53f956d5cd00691966842496a2ffcf24c7e88c4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 6 Aug 2025 21:47:43 +0900 Subject: [PATCH 441/685] =?UTF-8?q?refactor(OAuth):=20localhost=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EB=B0=B0=ED=8F=AC=20=ED=83=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-user-docker.yml | 6 +++--- docker-compose.user.yml | 2 +- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 9a68b401..0f7c440d 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -56,9 +56,9 @@ jobs: - name: Build and Push Docker Image run: | TAG=${GITHUB_SHA::7} - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest ./nowait-app-user-api - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_user:${TAG} -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_user:latest ./nowait-app-user-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_user:${TAG} + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_user:latest deploy: name: Deploy to EC2 diff --git a/docker-compose.user.yml b/docker-compose.user.yml index 3c08e4a4..708435f9 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -13,7 +13,7 @@ services: - mysql_data:/var/lib/mysql nowait-app-user-api: - image: jihuneddie/nowait_dev:latest + image: jihuneddie/nowait_dev_user:latest container_name: nowait-app-user-api ports: - "8081:8081" diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 10a37e531f042646eb8c5cf567f1804be0ad984a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 7 Aug 2025 23:55:36 +0900 Subject: [PATCH 442/685] =?UTF-8?q?refactor(Menu):=20=EC=86=8C=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/domaincorerdb/menu/entity/Menu.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java index 1ea3a632..fc1f35bc 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java @@ -26,7 +26,7 @@ public class Menu extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long Id; + private Long id; @Column(nullable = false) private Long storeId; @@ -52,7 +52,7 @@ public class Menu extends BaseTimeEntity { public Menu(LocalDateTime createdAt, Long id, Long storeId, String adminDisplayName, String name, String description, Integer price, Boolean isSoldOut, Boolean deleted) { super(createdAt); - this.Id = id; + this.id = id; this.storeId = storeId; this.adminDisplayName = adminDisplayName; this.name = name; From d015bb6ea5b6cd8d1099a1f2469ede5afe06cce8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 00:04:09 +0900 Subject: [PATCH 443/685] =?UTF-8?q?refactor(Menu):=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java index cec62e1d..466f2d50 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java @@ -26,6 +26,7 @@ public class MenuCreateResponse { public static MenuCreateResponse fromEntity(Menu menu) { return MenuCreateResponse.builder() .createdAt(menu.getCreatedAt()) + .adminDisplayName(menu.getAdminDisplayName()) .menuId(menu.getId()) .storeId(menu.getStoreId()) .adminDisplayName(menu.getAdminDisplayName()) From 2f73879357acc288f1026ffc5a094be6b810131f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 14:28:21 +0900 Subject: [PATCH 444/685] =?UTF-8?q?refactor(User):=20storeId=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/user/dto/ManagerLoginResponseDto.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java index d6c1ebdf..fd99b87e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerLoginResponseDto.java @@ -17,6 +17,8 @@ public class ManagerLoginResponseDto { private String email; @Schema(description = "닉네임", example = "무한이") private String nickname; + @Schema(description = "주점 Id", example = "1") + private Long storeId; @Schema(description = "JWT TOKEN", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNCIsImVtYWlsIjoiYXBpZG9jQGVtYWlsLmNvbSIsInJvbGVzIjpbIk1FTUJFUiJdLCJleHAiOjE3MTIyMjQ5NzV9.SQKtyHmqn2NKzHy4BX7_IgBePO_svEtmz1xbO9ToMz8") private String accessToken; @@ -25,6 +27,7 @@ public static ManagerLoginResponseDto fromEntity(User user,String token) { .userId(user.getId()) .email(user.getEmail()) .nickname(user.getNickname()) + .storeId(user.getStoreId()) .accessToken(token) .build(); } From a07cbeb1a39c85328f94a846444a489d15b39587 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 14:39:52 +0900 Subject: [PATCH 445/685] =?UTF-8?q?refactor(Swagger):=20api=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/controller/OrderController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index a45365ce..3e7e7efa 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -34,7 +34,7 @@ public class OrderController { private final OrderService orderService; @GetMapping("/{storeId}") - @Operation(summary = "주점별 주문리스트 조회", description = "특정 주점에 대한 예약리스트 조회") + @Operation(summary = "주점별 주문 리스트 조회", description = "특정 주점에 대한 주문 리스트 조회") @ApiResponse(responseCode = "200", description = "주문 리스트 조회") public ResponseEntity getOrderListByStoreId(@PathVariable Long storeId, @AuthenticationPrincipal MemberDetails memberDetails) { From 381a1dae2fe6051757ccfaf9af749d9c93fa24f0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 14:40:17 +0900 Subject: [PATCH 446/685] =?UTF-8?q?refactor(Oder):=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=EC=9A=A9=20=EC=9D=B4=EB=A6=84=20=EB=85=B8?= =?UTF-8?q?=EC=B6=9C=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationadmin/order/dto/OrderResponseDto.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java index ccca3fd6..d8a3979a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -26,13 +26,13 @@ public static OrderResponseDto fromEntity(UserOrder userOrder) { Map menuDetails = new LinkedHashMap<>(); for (OrderItem item : userOrder.getOrderItems()) { - String name = item.getMenu().getName(); + String displayName = item.getMenu().getAdminDisplayName(); int quantity = item.getQuantity(); int price = item.getMenu().getPrice(); // 메뉴 단가 // 메뉴명이 중복되면 수량만 누적 // merge 람다함수 활용(해당 key가 있으면 수량 누적, 없으면 새로 생성) - menuDetails.merge(name, + menuDetails.merge(displayName, new MenuDetail(quantity, price), (oldVal, newVal) -> new MenuDetail( oldVal.getQuantity() + newVal.getQuantity(), From 0e6989a49c3ec382931bb19269497c3f6feb6c85 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 15:00:17 +0900 Subject: [PATCH 447/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 6 ++++-- docker-compose.admin.yml | 2 +- docker-compose.user-monitoring.yml | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index 916204cd..b4fa498f 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -56,8 +56,10 @@ jobs: - name: Build and Push Docker Image run: | - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin ./nowait-app-admin-api - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:admin + TAG=${GITHUB_SHA::7} + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest ./nowait-app-admin-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest diff --git a/docker-compose.admin.yml b/docker-compose.admin.yml index d42dcaf6..ac0be6a7 100644 --- a/docker-compose.admin.yml +++ b/docker-compose.admin.yml @@ -13,7 +13,7 @@ services: - mysql_data:/var/lib/mysql nowait-app-admin-api: - image: jihuneddie/nowait_dev:admin + image: jihuneddie/nowait_dev:latest container_name: nowait-app-admin-api ports: - "8085:8085" diff --git a/docker-compose.user-monitoring.yml b/docker-compose.user-monitoring.yml index 549cdead..39b54dd7 100644 --- a/docker-compose.user-monitoring.yml +++ b/docker-compose.user-monitoring.yml @@ -1,6 +1,6 @@ services: prometheus-user: - image: prom/prometheus + image: prom/prometheus:latest container_name: prometheus-user ports: - "9090:9090" @@ -13,7 +13,7 @@ services: restart: always grafana-user: - image: grafana/grafana + image: grafana/grafana:latest container_name: grafana-user ports: - "3003:3000" From 4d7a6aab2a8b17a91ecbd34dc32a5a7c3f972589 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 15:02:53 +0900 Subject: [PATCH 448/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + From 58bdcaf55d4644fb91ad632b6c536d6fd1584b57 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 15:30:52 +0900 Subject: [PATCH 449/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-admin-docker.yml | 8 ++++---- .github/workflows/deploy-user-docker.yml | 2 +- docker-compose.admin.yml | 6 +++--- docker-compose.user.yml | 4 ++-- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-admin-docker.yml b/.github/workflows/deploy-admin-docker.yml index b4fa498f..fbcb3858 100644 --- a/.github/workflows/deploy-admin-docker.yml +++ b/.github/workflows/deploy-admin-docker.yml @@ -15,7 +15,7 @@ on: - 'gradle/**' env: - DOCKERHUB_REPO: jihuneddie/nowait_dev + DOCKERHUB_REPO: jihuneddie/nowait_dev_admin DIVISION: admin EC2_DEPLOY_PATH: /home/ubuntu/spring-github-action-admin-docker @@ -57,9 +57,9 @@ jobs: - name: Build and Push Docker Image run: | TAG=${GITHUB_SHA::7} - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest ./nowait-app-admin-api - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:${TAG} - docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev:latest + docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_admin:${TAG} -t ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_admin:latest ./nowait-app-admin-api + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_admin:${TAG} + docker push ${{ secrets.DOCKERHUB_USERNAME }}/nowait_dev_admin:latest diff --git a/.github/workflows/deploy-user-docker.yml b/.github/workflows/deploy-user-docker.yml index 0f7c440d..0683ae85 100644 --- a/.github/workflows/deploy-user-docker.yml +++ b/.github/workflows/deploy-user-docker.yml @@ -15,7 +15,7 @@ on: - 'gradle/**' env: - DOCKERHUB_REPO: jihuneddie/nowait_dev + DOCKERHUB_REPO: jihuneddie/nowait_dev_user DIVISION: user EC2_DEPLOY_PATH: /home/ubuntu/spring-github-action-user-docker diff --git a/docker-compose.admin.yml b/docker-compose.admin.yml index ac0be6a7..312bfcaa 100644 --- a/docker-compose.admin.yml +++ b/docker-compose.admin.yml @@ -13,14 +13,14 @@ services: - mysql_data:/var/lib/mysql nowait-app-admin-api: - image: jihuneddie/nowait_dev:latest + image: jihuneddie/nowait_dev_admin:latest container_name: nowait-app-admin-api ports: - "8085:8085" depends_on: - mysql - volumes: - - ./nowait-app-admin-api:/app +# volumes: +# - ./nowait-app-admin-api:/app restart: always volumes: diff --git a/docker-compose.user.yml b/docker-compose.user.yml index 708435f9..450ecc7c 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -19,8 +19,8 @@ services: - "8081:8081" depends_on: - mysql - volumes: - - ./nowait-app-user-api:/app +# volumes: +# - ./nowait-app-user-api:/app restart: always volumes: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,4 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From dcba11182541218515f2ed3d7f01215ede4e62c8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 15:39:07 +0900 Subject: [PATCH 450/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + scripts/deploy-admin-docker.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 0e1dadde..3c2f7733 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -19,8 +19,8 @@ else fi echo "3. start container" -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev pull nowait-app-admin-api prometheus-admin grafana-admin -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev up -d nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api prometheus-admin grafana-admin echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") From aa528b4a9d1a6e84ddd9974e24565b46dad82b79 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 15:51:17 +0900 Subject: [PATCH 451/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 + scripts/deploy-user-docker.sh | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..94a4f36a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -15,3 +15,4 @@ public static void main(String[] args) { } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index ad31b0f8..232d21e6 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -19,8 +19,8 @@ else fi echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev pull nowait-app-user-api prometheus-user grafana-user -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev up -d nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api prometheus-user grafana-user echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From a3ad3f53d5eb2e9c7448a1ae51f507a86ab23d14 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 16:01:16 +0900 Subject: [PATCH 452/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 2 -- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - scripts/deploy-admin-docker.sh | 3 +++ scripts/deploy-user-docker.sh | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 94a4f36a..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,5 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 3c2f7733..93297fbb 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -18,6 +18,9 @@ else echo "No admin container found." fi +echo "Cleaning up old containers…" +docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin down + echo "3. start container" sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api prometheus-admin grafana-admin sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api prometheus-admin grafana-admin diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 232d21e6..46c78a4f 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -15,9 +15,12 @@ if [ -n "$CONTAINER_ID" ]; then echo "Stopping container $CONTAINER_ID" docker rm -f "$CONTAINER_ID" else - echo "No admin container found." + echo "No User container found." fi +echo "Cleaning up old containers…" +docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user down + echo "3. start container" sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api prometheus-user grafana-user sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api prometheus-user grafana-user From 5e21fc8a278c5e6054d914bf4e964a14cbebe0a3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 16:23:58 +0900 Subject: [PATCH 453/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy-admin-docker.sh | 4 ++-- scripts/deploy-user-docker.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 93297fbb..1a19c8fa 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin down echo "3. start container" -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api prometheus-admin grafana-admin -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 46c78a4f..3bf2d6cf 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user down echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api prometheus-user grafana-user -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From de8deb947a740c0a1c927199b6065f6775a99a49 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 16:26:20 +0900 Subject: [PATCH 454/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + scripts/deploy-user-docker.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 3bf2d6cf..46c78a4f 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user down echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api prometheus-user grafana-user echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From 5b1fe08aa3d622f17962aa6e59d788488775ff44 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 17:47:16 +0900 Subject: [PATCH 455/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.admin.yml | 3 ++- docker-compose.user.yml | 3 ++- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docker-compose.admin.yml b/docker-compose.admin.yml index 312bfcaa..9538a350 100644 --- a/docker-compose.admin.yml +++ b/docker-compose.admin.yml @@ -1,7 +1,7 @@ services: mysql: image: mysql:8.0 - container_name: mysql_nowait_container + container_name: mysql_nowait_admin_container environment: MYSQL_ROOT_PASSWORD: gtable1118! MYSQL_DATABASE: gtable_dev @@ -22,6 +22,7 @@ services: # volumes: # - ./nowait-app-admin-api:/app restart: always + pull_policy: always volumes: mysql_data: diff --git a/docker-compose.user.yml b/docker-compose.user.yml index 450ecc7c..d996fb51 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -1,7 +1,7 @@ services: mysql: image: mysql:8.0 - container_name: mysql_nowait_container + container_name: mysql_nowait_user_container environment: MYSQL_ROOT_PASSWORD: gtable1118! MYSQL_DATABASE: gtable_dev @@ -22,6 +22,7 @@ services: # volumes: # - ./nowait-app-user-api:/app restart: always + pull_policy: always volumes: mysql_data: diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From e881ab3c6cbaa97af858a20ff2ab9cad7e4e342b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 17:58:01 +0900 Subject: [PATCH 456/685] =?UTF-8?q?refactor:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.user.yml | 2 +- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - .../src/main/java/com/nowait/ApiUserApplication.java | 1 + scripts/deploy-admin-docker.sh | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.user.yml b/docker-compose.user.yml index d996fb51..baab309f 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -8,7 +8,7 @@ services: MYSQL_USER: developer MYSQL_PASSWORD: gtable1118! ports: - - "3306:3306" + - "3307:3306" volumes: - mysql_data:/var/lib/mysql diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,4 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 1a19c8fa..93297fbb 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin down echo "3. start container" -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api prometheus-admin grafana-admin echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") From 01d7db019d47979a118e690425c45d70c55834cf Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:11:20 +0900 Subject: [PATCH 457/685] =?UTF-8?q?feat(Menu):=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=20=EC=88=9C=EC=84=9C=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20sortOrder=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/menu/dto/MenuCreateRequest.java | 1 + .../applicationadmin/menu/dto/MenuCreateResponse.java | 3 ++- .../com/nowait/applicationadmin/menu/dto/MenuReadDto.java | 3 ++- .../com/nowait/applicationuser/menu/dto/MenuReadDto.java | 2 ++ .../java/com/nowait/domaincorerdb/menu/entity/Menu.java | 7 +++++++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java index 4b5fc37c..89813c71 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateRequest.java @@ -31,6 +31,7 @@ public Menu toEntity() { .description(description) .price(price) .isSoldOut(false) + .sortOrder(0L) .deleted(false) .build(); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java index 466f2d50..41d2affe 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuCreateResponse.java @@ -4,7 +4,6 @@ import com.nowait.domaincorerdb.menu.entity.Menu; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -20,6 +19,7 @@ public class MenuCreateResponse { private String description; private Integer price; private Boolean isSoldOut; + private Long sortOrder; private Boolean deleted; private LocalDateTime createdAt; @@ -34,6 +34,7 @@ public static MenuCreateResponse fromEntity(Menu menu) { .description(menu.getDescription()) .price(menu.getPrice()) .isSoldOut(menu.getIsSoldOut()) + .sortOrder(menu.getSortOrder()) .deleted(menu.getDeleted()) .build(); } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java index a5e25126..d4d3dc36 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuReadDto.java @@ -4,7 +4,6 @@ import com.nowait.domaincorerdb.menu.entity.Menu; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -19,6 +18,7 @@ public class MenuReadDto { private String name; private String description; private Integer price; + private Long sortOrder; private Boolean isSoldOut; private Boolean deleted; private List images; @@ -31,6 +31,7 @@ public static MenuReadDto fromEntity(Menu menu, List im .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) + .sortOrder(menu.getSortOrder()) .isSoldOut(menu.getIsSoldOut()) .deleted(menu.getDeleted()) .images(images) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java index cf013289..ce8d6325 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadDto.java @@ -17,6 +17,7 @@ public class MenuReadDto { private String name; private String description; private Integer price; + private Long sortOrder; private Boolean isSoldOut; private Boolean deleted; private List images; @@ -28,6 +29,7 @@ public static MenuReadDto fromEntity(Menu menu, List im .name(menu.getName()) .description(menu.getDescription()) .price(menu.getPrice()) + .sortOrder(menu.getSortOrder()) .isSoldOut(menu.getIsSoldOut()) .deleted(menu.getDeleted()) .images(images) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java index fc1f35bc..c1297176 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java @@ -46,6 +46,9 @@ public class Menu extends BaseTimeEntity { @Column(nullable = false) private Boolean isSoldOut; + @Column(nullable = false) + private Long sortOrder; + @Column(nullable = false) private Boolean deleted; @@ -72,4 +75,8 @@ public void updateInfo(String adminDisplayName, String name, String description, public void markAsDeleted() { this.deleted = true; } public void toggleSoldOut() { this.isSoldOut = !this.isSoldOut; } + + public void updateSortOrder(Long sortOrder) { + this.sortOrder = sortOrder; + } } From a7fc5890d938082ab24fb90be29b7e0261e19978 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:11:31 +0900 Subject: [PATCH 458/685] =?UTF-8?q?feat(Menu):=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java index e4ee6369..3b28ee64 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuController.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.menu.controller; +import java.util.List; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -14,6 +16,7 @@ import com.nowait.applicationadmin.menu.dto.MenuCreateRequest; import com.nowait.applicationadmin.menu.dto.MenuCreateResponse; +import com.nowait.applicationadmin.menu.dto.MenuSortUpdateRequest; import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; import com.nowait.applicationadmin.menu.service.MenuService; import com.nowait.common.api.ApiUtils; @@ -125,4 +128,20 @@ public ResponseEntity toggleSoldOut(@PathVariable Long menuId) { ) ); } + + @PatchMapping("/update-sort") + @Operation(summary = "메뉴 순서 수정", description = "메뉴의 순서를 수정합니다.") + @ApiResponse(responseCode = "200", description = "메뉴 순서 수정") + public ResponseEntity updateMenuSortOrder( + @Valid @RequestBody List request, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + menuService.updateMenuSortOrder(request, memberDetails) + ) + ); + } } From 1bc506f648081bf11517f2a8c49142711b57e5fd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:11:47 +0900 Subject: [PATCH 459/685] =?UTF-8?q?feat(Menu):=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/service/MenuService.java | 23 ++++++++++++++++++- .../menu/service/MenuService.java | 2 +- .../menu/repository/MenuRepository.java | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 54972594..51fdba1c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -10,6 +10,7 @@ import com.nowait.applicationadmin.menu.dto.MenuImageUploadResponse; import com.nowait.applicationadmin.menu.dto.MenuReadDto; import com.nowait.applicationadmin.menu.dto.MenuReadResponse; +import com.nowait.applicationadmin.menu.dto.MenuSortUpdateRequest; import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.menu.entity.Menu; @@ -60,7 +61,7 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberD // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 validateMenuViewAuthorization(user, storeId); - List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); + List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() .map(menu -> { @@ -123,6 +124,26 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDeta return MenuReadDto.fromEntity(saved, imageDto); } + @Transactional + public String updateMenuSortOrder(List requests, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + + if (!Role.SUPER_ADMIN.equals(user.getRole())) { + throw new MenuUpdateUnauthorizedException(); + } + + requests.stream() + .map(request -> { + Menu menu = menuRepository.findById(request.getMenuId()) + .orElseThrow(MenuNotFoundException::new); + menu.updateSortOrder(request.getSortOrder()); + return menu; + }) + .forEach(menuRepository::save); + + return "메뉴 순서가 성공적으로 업데이트되었습니다."; + } + @Transactional public String deleteMenu(Long menuId, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index 2fc43c90..2ab8cfb7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -30,7 +30,7 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { if (storeId == null) { throw new MenuParamEmptyException(); } - List menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId); + List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() .map(menu -> { diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java index 80f07c95..b785836c 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java @@ -11,6 +11,7 @@ @Repository public interface MenuRepository extends JpaRepository { List findAllByStoreIdAndDeletedFalse(Long storeId); + List findAllByStoreIdAndDeletedFalseOrderBySortOrder(Long storeId); Optional findByStoreIdAndIdAndDeletedFalse(Long storeId, Long menuId); Optional findByIdAndDeletedFalse(Long menuId); } From 14b20503185d08a3402297387faa92687d824ab5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:11:53 +0900 Subject: [PATCH 460/685] =?UTF-8?q?feat(Menu):=20MenuSortUpdateRequest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/dto/MenuSortUpdateRequest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java new file mode 100644 index 00000000..fb4fd341 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java @@ -0,0 +1,15 @@ +package com.nowait.applicationadmin.menu.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MenuSortUpdateRequest { + private Long menuId; + private Long sortOrder; +} From d9de302a935f022915fbde721275713a34f6ecd9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:12:15 +0900 Subject: [PATCH 461/685] =?UTF-8?q?feat(Order):=20null=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/dto/OrderResponseDto.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java index d8a3979a..274063a4 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -1,6 +1,7 @@ package com.nowait.applicationadmin.order.dto; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -19,14 +20,17 @@ public class OrderResponseDto { private String depositorName; private Integer totalPrice; private OrderStatus status; - private Map menuDetails; + private HashMap menuDetails; private LocalDateTime createdAt; public static OrderResponseDto fromEntity(UserOrder userOrder) { - Map menuDetails = new LinkedHashMap<>(); + HashMap menuDetails = new LinkedHashMap<>(); for (OrderItem item : userOrder.getOrderItems()) { - String displayName = item.getMenu().getAdminDisplayName(); + String displayName = item.getMenu().getAdminDisplayName() == null + ? item.getMenu().getName() // 관리자 표시명이 없으면 일반 메뉴명 사용 + : item.getMenu().getAdminDisplayName(); + int quantity = item.getQuantity(); int price = item.getMenu().getPrice(); // 메뉴 단가 From 96527417d63dd9b41adf93e9747631af0bf815e2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:19:33 +0900 Subject: [PATCH 462/685] =?UTF-8?q?refactor(Order):=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/menu/dto/MenuSortUpdateRequest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java index fb4fd341..c9509e1f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/dto/MenuSortUpdateRequest.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.menu.dto; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -10,6 +12,9 @@ @NoArgsConstructor @Builder public class MenuSortUpdateRequest { + @NotNull private Long menuId; + @NotNull + @PositiveOrZero private Long sortOrder; } From 8e24938bf8b0c3b9673bc225586131d593c31340 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:21:37 +0900 Subject: [PATCH 463/685] =?UTF-8?q?refactor(Order):=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EB=A9=94=EB=89=B4=EB=AA=85=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/dto/OrderResponseDto.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java index 274063a4..8224813e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -20,16 +20,17 @@ public class OrderResponseDto { private String depositorName; private Integer totalPrice; private OrderStatus status; - private HashMap menuDetails; + private Map menuDetails; private LocalDateTime createdAt; public static OrderResponseDto fromEntity(UserOrder userOrder) { HashMap menuDetails = new LinkedHashMap<>(); for (OrderItem item : userOrder.getOrderItems()) { - String displayName = item.getMenu().getAdminDisplayName() == null - ? item.getMenu().getName() // 관리자 표시명이 없으면 일반 메뉴명 사용 - : item.getMenu().getAdminDisplayName(); + String adminDisplayName = item.getMenu().getAdminDisplayName(); + String displayName = (adminDisplayName == null || adminDisplayName.isBlank()) + ? item.getMenu().getName() // 관리자 표시명이 없거나 공백이면 일반 메뉴명 사용 + : adminDisplayName; int quantity = item.getQuantity(); int price = item.getMenu().getPrice(); // 메뉴 단가 From 609be6617b27114abbbd36c703bc1afd86a4160b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:27:12 +0900 Subject: [PATCH 464/685] =?UTF-8?q?refactor(Menu):=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/service/MenuService.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 51fdba1c..2bdd5589 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -1,6 +1,7 @@ package com.nowait.applicationadmin.menu.service; import java.util.List; +import java.util.Map; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -132,14 +133,36 @@ public String updateMenuSortOrder(List requests, MemberDe throw new MenuUpdateUnauthorizedException(); } - requests.stream() - .map(request -> { - Menu menu = menuRepository.findById(request.getMenuId()) - .orElseThrow(MenuNotFoundException::new); - menu.updateSortOrder(request.getSortOrder()); - return menu; - }) - .forEach(menuRepository::save); + if (requests == null || requests.isEmpty()) { + throw new MenuParamEmptyException(); + } + + if (requests.stream().map(MenuSortUpdateRequest::getMenuId).distinct().count() != requests.size()) { + throw new IllegalArgumentException("중복된 메뉴 ID가 포함되어 있습니다."); + } + + if (requests.stream().anyMatch(r -> r.getSortOrder() == null || r.getSortOrder() < 0)) { + throw new IllegalArgumentException("잘못된 정렬 순서가 포함되어 있습니다. 정렬 순서는 0 이상의 정수여야 합니다."); + } + + List ids = requests.stream().map(MenuSortUpdateRequest::getMenuId).toList(); + List menus = menuRepository.findAllById(ids); + if (menus.size() != ids.size()) { + throw new MenuNotFoundException(); + } + + Long storeId = menus.get(0).getStoreId(); + if (!menus.stream().allMatch(m -> storeId.equals(m.getStoreId()))) { + throw new IllegalArgumentException("다른 매장의 메뉴가 있습니다."); + } + + Map idToSort = requests.stream() + .collect(java.util.stream.Collectors.toMap(MenuSortUpdateRequest::getMenuId, + MenuSortUpdateRequest::getSortOrder)); + + menus.forEach(m -> m.updateSortOrder(idToSort.get(m.getId()))); + + menuRepository.saveAll(menus); return "메뉴 순서가 성공적으로 업데이트되었습니다."; } From 4d189d8b1b3ca70d662fe95f6f1b6911ec4a70ca Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 8 Aug 2025 21:27:54 +0900 Subject: [PATCH 465/685] =?UTF-8?q?refactor(Menu):=20HashMap=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/order/dto/OrderResponseDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java index 8224813e..3bd2e1b9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java @@ -20,7 +20,7 @@ public class OrderResponseDto { private String depositorName; private Integer totalPrice; private OrderStatus status; - private Map menuDetails; + private HashMap menuDetails; private LocalDateTime createdAt; public static OrderResponseDto fromEntity(UserOrder userOrder) { From d02581e2219408e7a8414a60f0397655e6a69588 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 10 Aug 2025 01:22:12 +0900 Subject: [PATCH 466/685] =?UTF-8?q?refactor:=20test=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index b2b40a9c..3a65d7ac 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -76,7 +76,12 @@ dependencies { // prometheus implementation 'io.micrometer:micrometer-registry-prometheus' + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation platform("org.testcontainers:testcontainers-bom:1.20.1") + testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:testcontainers" + } test { From 6f0a31ca2640405b1cd5918133465f045e04ccff Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 10 Aug 2025 01:22:29 +0900 Subject: [PATCH 467/685] =?UTF-8?q?test(Reservation):=20=EC=9C=A0=EB=8B=9B?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/ReservationServiceTest.java | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java new file mode 100644 index 00000000..c3757ca5 --- /dev/null +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java @@ -0,0 +1,298 @@ +package com.nowait.applicationadmin.reservation; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.springframework.data.redis.core.DefaultTypedTuple; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; +import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; +import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; +import com.nowait.applicationadmin.reservation.service.ReservationService; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.entity.MemberDetails; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.repository.UserRepository; + +public class ReservationServiceTest { + + @Mock + private ReservationRepository reservationRepository; + @Mock + private UserRepository userRepository; + @Mock + private WaitingRedisRepository waitingRedisRepository; + @Mock + private StoreRepository storeRepository; + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ZSetOperations zSetOps; + + + @InjectMocks + private ReservationService reservationService; + + private final Long storeId = 23L; + private final String userId = "10"; + private final AtomicLong seq = new AtomicLong(1000L); + private MemberDetails adminPrincipal; + private Store storeRef; + + private Answer stubSaveAssigningId() { + return inv -> { + Reservation r = inv.getArgument(0); + // 새 객체 만들어 ID 포함해서 반환 + return Reservation.builder() + .id(seq.incrementAndGet()) + .reservationNumber(r.getReservationNumber()) + .store(r.getStore()) + .user(r.getUser()) + .requestedAt(r.getRequestedAt()) + .updatedAt(r.getUpdatedAt()) + .status(r.getStatus()) + .partySize(r.getPartySize()) + .build(); + }; + } + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + adminPrincipal = MemberDetails.builder() + .id(1L).email("admin@test.com").password("pw") + .authorities(List.of(new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"))) + .build(); + + User adminUser = User.createUserWithId( + 1L, "admin@test.com", "admin", "img", + SocialType.LOCAL, Role.SUPER_ADMIN, storeId + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(adminUser)); + + storeRef = Store.builder() + .storeId(storeId) + .departmentId(10L) + .name("S") + .location("L") + .description("D") + .noticeTitle("NT") + .noticeContent("NC") + .openTime("10001100") + .isActive(true) + .deleted(false) + .build(); + + given(storeRepository.getReferenceById(storeId)).willReturn(storeRef); + + given(redisTemplate.opsForZSet()).willReturn(zSetOps); + } + + @Test + @DisplayName("WAITING → CALLING") + void process_waiting_to_calling() { + // given + given(waitingRedisRepository.getReservationId(storeId, userId)).willReturn("R-001"); + given(waitingRedisRepository.getWaitingStatus(storeId, userId)).willReturn("WAITING"); + given(waitingRedisRepository.getWaitingPartySize(storeId, userId)).willReturn(3); + given(zSetOps.score("waiting:" + storeId, userId)).willReturn((double) System.currentTimeMillis()); + User userRef = User.createUserWithId(200L, "u@a", "neo", "img", SocialType.KAKAO, Role.USER, storeId); + given(userRepository.getReferenceById(anyLong())).willReturn(userRef); + + + // when + EntryStatusResponseDto dto = reservationService.processEntryStatus( + storeId, userId, adminPrincipal, ReservationStatus.CALLING); + + // then + assertThat(dto.getStatus()).isEqualTo("CALLING"); + assertThat(dto.getReservationNumber()).isEqualTo("R-001"); + then(waitingRedisRepository).should().setWaitingStatus(storeId, userId, "CALLING"); + then(waitingRedisRepository).should().setWaitingCalledAt(eq(storeId), eq(userId), anyLong()); + then(reservationRepository).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("WAITING → CONFIRMED : DB 저장 + Redis 삭제") + void process_waiting_to_confirmed() { + // given + given(waitingRedisRepository.getReservationId(storeId, userId)).willReturn("R-002"); + given(waitingRedisRepository.getWaitingStatus(storeId, userId)).willReturn("WAITING"); + given(waitingRedisRepository.getWaitingPartySize(storeId, userId)).willReturn(2); + given(zSetOps.score("waiting:" + storeId, userId)).willReturn((double) System.currentTimeMillis()); + given(userRepository.getReferenceById(anyLong())) + .willReturn(User.createUserWithId(200L, "u@a", "neo", "img", SocialType.KAKAO, Role.USER, storeId)); + + AtomicReference savedRef = new AtomicReference<>(); + given(reservationRepository.save(any(Reservation.class))).willAnswer(inv -> { + Reservation saved = stubSaveAssigningId().answer(inv); // ★ ID 있는 객체 + savedRef.set(saved); + return saved; + }); + + // when + EntryStatusResponseDto dto = reservationService.processEntryStatus( + storeId, userId, adminPrincipal, ReservationStatus.CONFIRMED); + + // then + then(waitingRedisRepository).should().deleteWaiting(storeId, userId); + assertThat(savedRef.get().getStatus()).isEqualTo(ReservationStatus.CONFIRMED); + assertThat(dto.getStatus()).isEqualTo("CONFIRMED"); + assertThat(dto.getReservationNumber()).isEqualTo("R-002"); + } + + + @Test + @DisplayName("CALLING → CONFIRMED : DB 저장 + Redis 삭제") + void process_calling_to_confirmed() { + // given + given(waitingRedisRepository.getReservationId(storeId, userId)).willReturn("R-003"); + given(waitingRedisRepository.getWaitingStatus(storeId, userId)).willReturn("CALLING"); + given(waitingRedisRepository.getWaitingPartySize(storeId, userId)).willReturn(4); + given(zSetOps.score("waiting:" + storeId, userId)).willReturn((double) System.currentTimeMillis()); + given(userRepository.getReferenceById(anyLong())) + .willReturn(User.createUserWithId(200L, "u@a", "neo", "img", SocialType.KAKAO, Role.USER, storeId)); + given(reservationRepository.save(any(Reservation.class))).willAnswer(stubSaveAssigningId()); // ★ + + // when + EntryStatusResponseDto dto = reservationService.processEntryStatus( + storeId, userId, adminPrincipal, ReservationStatus.CONFIRMED); + + // then + then(waitingRedisRepository).should().deleteWaiting(storeId, userId); + assertThat(dto.getStatus()).isEqualTo("CONFIRMED"); + } + + + @Test + @DisplayName("CANCELLED 상태에서 CALLING 시도 → 예외") + void process_cancelled_to_calling_illegal() { + // given + given(waitingRedisRepository.getWaitingStatus(storeId, userId)).willReturn("CANCELLED"); + + // when / then + assertThatThrownBy(() -> + reservationService.processEntryStatus(storeId, userId, adminPrincipal, ReservationStatus.CALLING) + ).isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("WAITING → CANCELLED : DB 저장 + Redis 삭제") + void process_waiting_to_cancelled() { + // given + given(waitingRedisRepository.getReservationId(storeId, userId)).willReturn("R-004"); + given(waitingRedisRepository.getWaitingStatus(storeId, userId)).willReturn("WAITING"); + given(waitingRedisRepository.getWaitingPartySize(storeId, userId)).willReturn(2); + given(zSetOps.score("waiting:" + storeId, userId)).willReturn((double) System.currentTimeMillis()); + given(userRepository.getReferenceById(anyLong())) + .willReturn(User.createUserWithId(200L, "u@a", "neo", "img", SocialType.KAKAO, Role.USER, storeId)); + given(reservationRepository.save(any(Reservation.class))).willAnswer(stubSaveAssigningId()); // ★ + + // when + EntryStatusResponseDto dto = reservationService.processEntryStatus( + storeId, userId, adminPrincipal, ReservationStatus.CANCELLED); + + // then + then(waitingRedisRepository).should().deleteWaiting(storeId, userId); + assertThat(dto.getStatus()).isEqualTo("CANCELLED"); + } + + + @Test + @DisplayName("이미 CANCELLED 기록 존재 시 → CONFIRMED로 전환 (DB 조회 분기)") + void process_from_cancelled_record_to_confirmed() { + // given + given(waitingRedisRepository.getWaitingStatus(storeId, userId)).willReturn("CANCELLED"); + Long uid = 200L; + + User userRef = User.createUserWithId(uid, "u@a", "neo", "img", SocialType.KAKAO, Role.USER, storeId); + Reservation cancelled = Reservation.builder() + .id(777L) + .reservationNumber("R-005") + .store(storeRef) + .user(userRef) + .requestedAt(LocalDateTime.now().minusMinutes(30)) + .updatedAt(LocalDateTime.now().minusMinutes(20)) + .status(ReservationStatus.CANCELLED) + .partySize(3) + .build(); + + given(reservationRepository.findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( + eq(storeId), eq(uid), eq(List.of(ReservationStatus.CANCELLED)), any(), any() + )).willReturn(Optional.of(cancelled)); + given(userRepository.getReferenceById(anyLong())).willReturn(userRef); + given(reservationRepository.save(any(Reservation.class))).willAnswer(stubSaveAssigningId()); // ★ + + // when + EntryStatusResponseDto dto = reservationService.processEntryStatus( + storeId, String.valueOf(uid), adminPrincipal, ReservationStatus.CONFIRMED); + + // then + then(reservationRepository).should().save(any(Reservation.class)); + assertThat(dto.getStatus()).isEqualTo("CONFIRMED"); + assertThat(dto.getReservationNumber()).isEqualTo("R-005"); + } + + + @Test + @DisplayName("getAllWaitingUserDetails : 파이프라인 매핑") + void get_all_waiting_pipeline_mapping() { + // given + long t1 = System.currentTimeMillis() - 1000; + long t2 = System.currentTimeMillis(); + var a = new DefaultTypedTuple<>("200", (double) t1); + var b = new DefaultTypedTuple<>("201", (double) t2); + given(waitingRedisRepository.getAllWaitingWithScore(storeId)).willReturn(List.of(a, b)); + + given(userRepository.findAllById(Arrays.asList(200L, 201L))) + .willReturn(List.of( + User.createUserWithId(200L, "u1@a", "neo", "img", SocialType.KAKAO, Role.USER, storeId), + User.createUserWithId(201L, "u2@a", "trinity", "img", SocialType.KAKAO, Role.USER, storeId) + )); + + List pipeline = new ArrayList<>(); + pipeline.add("3"); pipeline.add("WAITING"); pipeline.add("R-100"); pipeline.add(String.valueOf(t1)); pipeline.add((double) t1); + pipeline.add("2"); pipeline.add("CALLING"); pipeline.add("R-101"); pipeline.add(String.valueOf(t2)); pipeline.add((double) t2); + given(redisTemplate.executePipelined(any(RedisCallback.class))).willReturn(pipeline); + + // when + List list = reservationService.getAllWaitingUserDetails(storeId); + + // then + assertThat(list).hasSize(2); + assertThat(list.get(0).getUserId()).isEqualTo("200"); + assertThat(list.get(0).getUserName()).isEqualTo("neo"); + assertThat(list.get(0).getReservationNumber()).isEqualTo("R-100"); + assertThat(list.get(0).getStatus()).isEqualTo("WAITING"); + assertThat(list.get(1).getStatus()).isEqualTo("CALLING"); + assertThat(list.get(0).getCreatedAt()).isBefore(list.get(1).getCreatedAt()); + } +} From 2d365e466e2b2a512bbdd9aacdb79ff210e9d924 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 10 Aug 2025 01:22:32 +0900 Subject: [PATCH 468/685] =?UTF-8?q?test(Reservation):=20=EC=9C=A0=EB=8B=9B?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WaitingRedisRepositoryTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java new file mode 100644 index 00000000..841c1ea7 --- /dev/null +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java @@ -0,0 +1,75 @@ +package com.nowait.applicationadmin.reservation; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; + +import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; + +public class WaitingRedisRepositoryTest { + @Mock + private StringRedisTemplate template; + @Mock private ZSetOperations zOps; + @Mock private HashOperations hOps; + + @InjectMocks + private WaitingRedisRepository repo; + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + given(template.opsForZSet()).willReturn(zOps); + given(template.opsForHash()).willReturn(hOps); + } + + @Test + @DisplayName("getAllWaitingWithScore : null-safe") + void get_all_waiting_with_score_null_safe() { + // given + given(zOps.rangeWithScores("waiting:100", 0, -1)).willReturn(null); + + // when + var res = repo.getAllWaitingWithScore(100L); + + // then + assertThat(res).isEmpty(); + } + + @Test + @DisplayName("getWaitingStatus : null-safe") + void get_waiting_status_null_safe() { + // given + given(hOps.get("waiting:status:100", "u")).willReturn(null); + + // when + var v = repo.getWaitingStatus(100L, "u"); + + // then + assertThat(v).isNull(); + } + + @Test + @DisplayName("deleteWaiting : 모든 관련 키 삭제") + void delete_waiting_remove_all_keys() { + // given (nothing) + + // when + repo.deleteWaiting(100L, "u"); + + // then + then(hOps).should().delete("waiting:status:100", "u"); + then(zOps).should().remove("waiting:100", "u"); + then(hOps).should().delete("waiting:party:100", "u"); + then(hOps).should().delete("reservation:number:100", "u"); + then(hOps).should().delete("waiting:calledAt:100", "u"); + } +} From efcc884286ca63f57b78970a931bf339b189fa52 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 10 Aug 2025 01:22:47 +0900 Subject: [PATCH 469/685] =?UTF-8?q?test(Reservation):=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20case=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/WaitingDuplicateIT.java | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingDuplicateIT.java diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingDuplicateIT.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingDuplicateIT.java new file mode 100644 index 00000000..f0c43b82 --- /dev/null +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingDuplicateIT.java @@ -0,0 +1,180 @@ +package com.nowait.applicationadmin.reservation; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + +@Testcontainers +public class WaitingDuplicateIT { + @Container + static GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); + + StringRedisTemplate tpl; + + Long storeId = 100L; + String zKey, stKey, partyKey, numKey, calledKey; + + @BeforeEach + void init() { + var cfg = new RedisStandaloneConfiguration("localhost", redis.getMappedPort(6379)); + var lf = new LettuceConnectionFactory(cfg); + lf.afterPropertiesSet(); + tpl = new StringRedisTemplate(lf); + + zKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; // waiting:{storeId} + stKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; // waiting:status:{storeId} + partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; // waiting:party:{storeId} + numKey = RedisKeyUtils.buildReservationNumberKey(storeId); // reservation:number:{storeId} + calledKey = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; // waiting:calledAt:{storeId} + + // clean + tpl.delete(List.of(zKey, stKey, partyKey, numKey, calledKey)); + } + + // ===== Helper: 현재 목록 덤프 ===== + private List dump() { + Set ids = tpl.opsForZSet().range(zKey, 0, -1); + if (ids == null) return List.of(); + List out = new ArrayList<>(); + for (String uid : ids) { + String status = (String) tpl.opsForHash().get(stKey, uid); + String party = (String) tpl.opsForHash().get(partyKey, uid); + String num = (String) tpl.opsForHash().get(numKey, uid); + out.add(uid + "|" + status + "|" + party + "|" + num); + } + return out; + } + + // ====== 시나리오 A: ID 정규화 실패(공백 차이) ====== + @Test + @DisplayName("A) userId 공백/포맷 불일치 → 사실상 같은 팀이 두 줄로 보임") + void duplicate_by_userid_format() { + // given + String uid1 = "200"; + String uid2 = " 200"; // 앞 공백 (의도적인 비정규화) + long now = System.currentTimeMillis(); + + // when + tpl.opsForZSet().add(zKey, uid1, now); + tpl.opsForHash().put(stKey, uid1, "WAITING"); + tpl.opsForHash().put(partyKey, uid1, "3"); + tpl.opsForHash().put(numKey, uid1, "R-100"); + + tpl.opsForZSet().add(zKey, uid2, now + 1); // 공백 다른 멤버 → ZSET에선 “다른 사용자” + tpl.opsForHash().put(stKey, uid2, "WAITING"); + tpl.opsForHash().put(partyKey, uid2, "3"); + tpl.opsForHash().put(numKey, uid2, "R-100"); // 같은 예약번호까지 + + // then + List view = dump(); + assertThat(view).hasSize(2); // UI 관점엔 중복으로 보임 + // 방어 로직 없다면 동일 사용자로 인식될 수 있음 + } + + // ====== 시나리오 B: 예약번호 충돌(비원자 생성/할당 가정) ====== + @Test + @DisplayName("B) 예약번호 충돌 → 서로 다른 userId가 같은 reservationNumber로 노출") + void duplicate_by_reservation_number_collision() throws Exception { + // given + String u1 = "201"; + String u2 = "202"; + long t = System.currentTimeMillis(); + + // when (의도적으로 같은 번호 세팅) + tpl.opsForZSet().add(zKey, u1, t); + tpl.opsForHash().put(stKey, u1, "WAITING"); + tpl.opsForHash().put(partyKey, u1, "2"); + tpl.opsForHash().put(numKey, u1, "R-200"); + + tpl.opsForZSet().add(zKey, u2, t + 1); + tpl.opsForHash().put(stKey, u2, "WAITING"); + tpl.opsForHash().put(partyKey, u2, "2"); + tpl.opsForHash().put(numKey, u2, "R-200"); // 충돌 + + // then + List view = dump(); + assertThat(view).filteredOn(s -> s.endsWith("|R-200")).hasSize(2); // 같은 번호 2개 + // DB unique는 막지만 Redis 뷰에선 중복 그대로 노출됨 + } + + // ====== 시나리오 C: 부분 삭제 레이스(유령 조각) ====== + @Test + @DisplayName("C) 부분 삭제 실패 → 찌꺼기 남아 중복/유령 항목 노출") + void ghost_after_partial_delete() { + // given + String uid = "203"; + long t = System.currentTimeMillis(); + tpl.opsForZSet().add(zKey, uid, t); + tpl.opsForHash().put(stKey, uid, "WAITING"); + tpl.opsForHash().put(partyKey, uid, "4"); + tpl.opsForHash().put(numKey, uid, "R-300"); + + // when (의도적으로 해시만 지우고 ZSET은 남김 = 네 현재 deleteWaiting 순차 호출이 실패한 상황을 흉내) + tpl.opsForHash().delete(stKey, uid); + tpl.opsForHash().delete(partyKey, uid); + tpl.opsForHash().delete(numKey, uid); + // tpl.opsForZSet().remove(zKey, uid); // 실패했다고 가정 + + // then + var ids = tpl.opsForZSet().range(zKey, 0, -1); + assertThat(ids).contains(uid); // 큐엔 남아있음 + List view = dump(); + // status/party/number가 null → 조회/매핑에서 NPE or 비정상 라인 가능 + assertThat(view.get(0)).contains("null"); + } + + // ====== 시나리오 D(선택): 동시 등록 레이스 증폭 ====== + @Test + @DisplayName("D) 동시 등록 100개(같은 사용자/다른 포맷) → 중복 노출 가능성 확인") + void concurrent_register_format_mixture() throws Exception { + // given + ExecutorService es = Executors.newFixedThreadPool(16); + CountDownLatch start = new CountDownLatch(1); + CountDownLatch done = new CountDownLatch(100); + + // when + for (int i = 0; i < 100; i++) { + final int k = i; + es.submit(() -> { + try { + start.await(); + String uid = (k % 2 == 0) ? "200" : " 200"; // 포맷 섞기 + long score = System.currentTimeMillis() + k; + tpl.opsForZSet().add(zKey, uid, score); + tpl.opsForHash().put(stKey, uid, "WAITING"); + tpl.opsForHash().put(partyKey, uid, "3"); + tpl.opsForHash().put(numKey, uid, "R-BURST"); + } catch (Exception ignore) { + } finally { + done.countDown(); + } + }); + } + start.countDown(); + done.await(10, TimeUnit.SECONDS); + es.shutdown(); + + // then + var view = dump(); + // 공백/포맷이 다른 멤버가 둘 다 남아있어 2줄 이상일 가능성 + assertThat(view.stream().filter(s -> s.endsWith("|R-BURST")).count()).isGreaterThanOrEqualTo(2); + } +} From c1150f682669ca223cc915c5ac2345e4b21129a3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 16:56:39 +0900 Subject: [PATCH 470/685] =?UTF-8?q?refactor(Menu):=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EC=A3=BC=EC=A0=90=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20404=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/menu/service/MenuService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index 2ab8cfb7..2a5c687f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -14,6 +14,8 @@ import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.repository.StoreRepository; import lombok.RequiredArgsConstructor; @@ -23,6 +25,7 @@ public class MenuService { private final MenuRepository menuRepository; private final MenuImageRepository menuImageRepository; + private final StoreRepository storeRepository; @Transactional(readOnly = true) @@ -30,6 +33,10 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { if (storeId == null) { throw new MenuParamEmptyException(); } + + storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() @@ -51,6 +58,9 @@ public MenuReadDto getMenuById(Long storeId, Long menuId) { throw new MenuParamEmptyException(); } + storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) .orElseThrow(MenuNotFoundException::new); From bcb9c7148a3c4adb1a47542aacb82c349bd8bebe Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 20:08:29 +0900 Subject: [PATCH 471/685] =?UTF-8?q?feat(Reservation):=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=ED=8C=85=20=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 30 +++++ .../WaitingPermitLuaRepository.java | 108 ++++++++++++++++++ .../WaitingPermitLuaRepositoryTest.java | 104 +++++++++++++++++ .../nowait/common/exception/ErrorMessage.java | 3 + .../UserWaitingLimitExceededException.java | 10 ++ 5 files changed, 255 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java create mode 100644 nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index e332ac26..9b4c5f4d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -29,7 +29,10 @@ import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; +import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; @@ -244,6 +247,33 @@ public ErrorResponse handleStorePaymentNotFoundException(StorePaymentNotFoundExc return new ErrorResponse(e.getMessage(), STORE_PAYMENT_NOT_FOUND.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(UserWaitingLimitExceededException.class) + public ErrorResponse handleUserWaitingLimitExceededException( + UserWaitingLimitExceededException e, WebRequest request) { + alarm(e, request); + log.error("handleUserWaitingLimitExceededException", e); + return new ErrorResponse(e.getMessage(), USER_WAITING_LIMIT_EXCEEDED.getCode()); + } + + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(ReservationNumberIssueFailException.class) + public ErrorResponse handleReservationNumberIssueFailException( + ReservationNumberIssueFailException e, WebRequest request) { + alarm(e, request); + log.error("handleReservationNumberIssueFailException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_NUMBER_ISSUE_FAIL.getCode()); + } + + @ResponseStatus(CONFLICT) + @ExceptionHandler(ReservationAddUnauthorizedException.class) + public ErrorResponse handleReservationAddUnauthorizedException( + ReservationAddUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("handleReservationAddUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ADD_UNAUTHORIZED.getCode()); + } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java new file mode 100644 index 00000000..8804919f --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java @@ -0,0 +1,108 @@ +package com.nowait.applicationuser.reservation.repository; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.data.redis.connection.ReturnType; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class WaitingPermitLuaRepository { + + private final StringRedisTemplate redis; + + private static final String ACQUIRE_SCRIPT = + "redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[1]);" + + "local holding = redis.call('ZCARD', KEYS[1]);" + + "local active = redis.call('SCARD', KEYS[2]);" + + "if (holding + active) >= tonumber(ARGV[3]) then return 0 end;" + + "redis.call('ZADD', KEYS[1], tonumber(ARGV[1]) + tonumber(ARGV[2]), ARGV[4]);" + + "return 1;"; + + private static final String FINALIZE_SCRIPT = + "redis.call('ZREM', KEYS[1], ARGV[1]);" + + "redis.call('SADD', KEYS[2], ARGV[2]);" + + "return 1;"; + + public boolean acquireLease(String userId, String token, long nowMs, long leaseMs, int limit, Duration ttlTo3am) { + final String hk = RedisKeyUtils.buildUserHoldingKey(userId); // u:{uid}:holding + final String ak = RedisKeyUtils.buildUserActiveKey(userId); // u:{uid}:active + + Long ok = redis.execute((RedisCallback) conn -> { + Object res = conn.eval( + ACQUIRE_SCRIPT.getBytes(StandardCharsets.UTF_8), + ReturnType.INTEGER, + 2, + raw(hk), raw(ak), + raw(Long.toString(nowMs)), + raw(Long.toString(leaseMs)), + raw(Integer.toString(limit)), + raw(token) + ); + // TTL 정렬(스크립트 밖에서) + conn.pExpire(raw(hk), ttlTo3am.toMillis()); + conn.pExpire(raw(ak), ttlTo3am.toMillis()); + return (Long) res; + }); + return ok != null && ok == 1L; + } + + public void finalizeActive(String userId, String token, String storeId, String reservationId, Duration ttlTo3am) { + final String hk = RedisKeyUtils.buildUserHoldingKey(userId); + final String ak = RedisKeyUtils.buildUserActiveKey(userId); + final String member = storeId + ":" + reservationId; + + redis.execute((RedisCallback) conn -> { + conn.eval( + FINALIZE_SCRIPT.getBytes(StandardCharsets.UTF_8), + ReturnType.INTEGER, + 2, + raw(hk), raw(ak), + raw(token), raw(member) + ); + conn.pExpire(raw(ak), ttlTo3am.toMillis()); + return null; + }); + } + + public void releaseLease(String userId, String token) { + final String hk = RedisKeyUtils.buildUserHoldingKey(userId); + redis.execute((RedisCallback) conn -> { + conn.zRem(raw(hk), raw(token)); + return null; + }); + } + + public Set getActiveMembers(String userId) { + final String ak = RedisKeyUtils.buildUserActiveKey(userId); + return redis.execute((RedisCallback>) conn -> { + Set raw = conn.sMembers(raw(ak)); + if (raw == null || raw.isEmpty()) return Collections.emptySet(); + Set out = new HashSet<>(raw.size()); + for (byte[] b : raw) out.add(string(b)); + return out; + }); + } + + public void removeActiveMember(String userId, String storeId, String reservationId) { + final String ak = RedisKeyUtils.buildUserActiveKey(userId); + final String member = storeId + ":" + reservationId; + redis.execute((RedisCallback) conn -> { + conn.sRem(raw(ak), raw(member)); + return null; + }); + } + + private byte[] raw(String s) { return redis.getStringSerializer().serialize(s); } + private String string(byte[] b) { return redis.getStringSerializer().deserialize(b); } +} diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java new file mode 100644 index 00000000..6f22a617 --- /dev/null +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java @@ -0,0 +1,104 @@ +package com.nowait.applicationuser.reservation.service; + +import static org.assertj.core.api.Assertions.*; + +import java.time.Duration; +import java.util.Set; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + +@Testcontainers +public class WaitingPermitLuaRepositoryTest { + + @Container + static GenericContainer redis = new GenericContainer<>("redis:7.2-alpine").withExposedPorts(6379); + + static StringRedisTemplate template; + static WaitingPermitLuaRepository permitRepo; + + final String userId = "U1"; + final Duration ttlTo3am = Duration.ofHours(6); // 테스트용 + final long leaseMs = 30_000; + + @BeforeAll + static void setupAll() { + var factory = new LettuceConnectionFactory(redis.getHost(), redis.getMappedPort(6379)); + factory.afterPropertiesSet(); + template = new StringRedisTemplate(factory); + template.afterPropertiesSet(); + permitRepo = new WaitingPermitLuaRepository(template); + } + + @BeforeEach + void flush() { + template.getConnectionFactory().getConnection().serverCommands().flushAll(); + } + + @Test + void acquireLease_underLimit_succeedsUpTo3() { + String hk = RedisKeyUtils.buildUserHoldingKey(userId); + String ak = RedisKeyUtils.buildUserActiveKey(userId); + long now = System.currentTimeMillis(); + + // 1,2,3번째 임대 성공 + assertThat(permitRepo.acquireLease(userId, "t1", now, leaseMs, 3, ttlTo3am)).isTrue(); + assertThat(permitRepo.acquireLease(userId, "t2", now, leaseMs, 3, ttlTo3am)).isTrue(); + assertThat(permitRepo.acquireLease(userId, "t3", now, leaseMs, 3, ttlTo3am)).isTrue(); + + // 4번째는 실패 + assertThat(permitRepo.acquireLease(userId, "t4", now, leaseMs, 3, ttlTo3am)).isFalse(); + + // holding 3개 확인 + Long holding = template.opsForZSet().zCard(hk); + Long active = template.opsForSet().size(ak); + assertThat(holding).isEqualTo(3L); + assertThat(active).isEqualTo(0L); + } + + @Test + void finalize_movesFromHoldingToActive_totalCountMaintained() { + long now = System.currentTimeMillis(); + // 두 개 임대 획득 + assertThat(permitRepo.acquireLease(userId, "t1", now, leaseMs, 3, ttlTo3am)).isTrue(); + assertThat(permitRepo.acquireLease(userId, "t2", now, leaseMs, 3, ttlTo3am)).isTrue(); + + // t1 확정 + permitRepo.finalizeActive(userId, "t1", "10", "10-20250101-0001", ttlTo3am); + + String hk = RedisKeyUtils.buildUserHoldingKey(userId); + String ak = RedisKeyUtils.buildUserActiveKey(userId); + + assertThat(template.opsForZSet().zCard(hk)).isEqualTo(1L); // t2만 holding + assertThat(template.opsForSet().size(ak)).isEqualTo(1L); // active 1 + assertThat(template.opsForSet().isMember(ak, "10:10-20250101-0001")).isTrue(); + } + + @Test + void releaseLease_removesFromHolding() { + long now = System.currentTimeMillis(); + assertThat(permitRepo.acquireLease(userId, "t1", now, leaseMs, 3, ttlTo3am)).isTrue(); + permitRepo.releaseLease(userId, "t1"); + + String hk = RedisKeyUtils.buildUserHoldingKey(userId); + assertThat(template.opsForZSet().zCard(hk)).isEqualTo(0L); + } + + @Test + void getActiveMembers_returnsSetMembers() { + long now = System.currentTimeMillis(); + permitRepo.acquireLease(userId, "t1", now, leaseMs, 3, ttlTo3am); + permitRepo.finalizeActive(userId, "t1", "77", "77-20250101-0001", ttlTo3am); + Set ms = permitRepo.getActiveMembers(userId); + assertThat(ms).containsExactly("77:77-20250101-0001"); + } +} diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index b8a416da..be45f308 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -28,6 +28,9 @@ public enum ErrorMessage { RESERVATION_VIEW_UNAUTHORIZED("예약 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation002"), RESERVATION_UPDATE_UNAUTHORIZED("예약 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation003"), DUPLICATE_RESERVATION("이미 대기 중인 예약이 존재합니다.", "reservation004"), + USER_WAITING_LIMIT_EXCEEDED("유저당 웨이팅 가능 개수(3개)를 초과했습니다.", "reservation005"), + RESERVATION_NUMBER_ISSUE_FAIL("예약 번호 발급에 실패했습니다.", "reservation006"), + RESERVATION_ADD_UNAUTHORIZED("MANAGER는 예약 대기를 할 수 없습니다.", "reservation007"), // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java new file mode 100644 index 00000000..dccdff12 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class UserWaitingLimitExceededException extends RuntimeException { + public UserWaitingLimitExceededException() { + super(ErrorMessage.USER_WAITING_LIMIT_EXCEEDED.getMessage()); + } +} + From e88fcb5b531071b9bc3deb4c9c7ca75cc55d0a82 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 20:08:39 +0900 Subject: [PATCH 472/685] =?UTF-8?q?feat(Reservation):=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=ED=8C=85=20=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A0=88=EB=94=94=EC=8A=A4=20=ED=82=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincoreredis/common/util/RedisKeyUtils.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index ca37768d..f40d5690 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -22,6 +22,15 @@ public class RedisKeyUtils { private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:"; private static final String WAITING_STATUS_KEY_PREFIX = "waiting:status:"; + // Waiting User keys + public static String buildUserHoldingKey(String userId) { + return "waiting:user:{" + userId + "}:holding"; // ZSET(member=token, score=expireEpochMs) + } + public static String buildUserActiveKey(String userId) { + return "waiting:user:{" + userId + "}:active"; // SET(member="storeId:reservationId") + } + + private RedisKeyUtils() { throw new UnsupportedOperationException("유틸리티 서비스는 인스턴스화 할 수 없습니다."); } From 56da2119d32edb985373cd8e61b70b6c685a82b3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 20:08:45 +0900 Subject: [PATCH 473/685] =?UTF-8?q?feat(Reservation):=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=ED=8C=85=20=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ReservationAddUnauthorizedException.java | 10 ++++++++++ .../exception/ReservationNumberIssueFailException.java | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAddUnauthorizedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNumberIssueFailException.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAddUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAddUnauthorizedException.java new file mode 100644 index 00000000..2f506793 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAddUnauthorizedException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationAddUnauthorizedException extends RuntimeException { + public ReservationAddUnauthorizedException() { + super(ErrorMessage.RESERVATION_ADD_UNAUTHORIZED.getMessage()); + } +} + diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNumberIssueFailException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNumberIssueFailException.java new file mode 100644 index 00000000..30cf81c2 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNumberIssueFailException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationNumberIssueFailException extends RuntimeException { + public ReservationNumberIssueFailException() { + super(ErrorMessage.RESERVATION_NUMBER_ISSUE_FAIL.getMessage()); + } +} + From 3250d1d6dd6f4944a769bbab875d5600a9da9d18 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 20:09:05 +0900 Subject: [PATCH 474/685] =?UTF-8?q?feat(Reservation):=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=ED=8C=85=20=EB=A1=9C=EC=A7=81=20=EC=9D=BC=EB=B6=80=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 153 ++++++++++++++---- 1 file changed, 121 insertions(+), 32 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 4b3c3edd..69c5956a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -1,5 +1,6 @@ package com.nowait.applicationuser.reservation.service; +import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -18,10 +19,10 @@ import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto; -import com.nowait.applicationuser.reservation.dto.MyWaitingStoreInfo; import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; @@ -29,6 +30,9 @@ import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; +import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; @@ -55,41 +59,116 @@ public class ReservationService { private final WaitingUserRedisRepository waitingUserRedisRepository; private final DepartmentRepository departmentRepository; private final StoreImageRepository storeImageRepository; + private final WaitingPermitLuaRepository waitingPermitLuaRepository; private final RedisTemplate redisTemplate; - - public WaitingResponseDto registerWaiting( - Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto - ) { - // Store 유효성 검증 추가 - Store store = storeRepository.findById(storeId) - .orElseThrow(StoreNotFoundException::new); + private static final int USER_LIMIT = 3; + private static final long LEASE_MS = 30_000; // 20~60초 권장 + + /** + * 웨이팅 등록 기존 로직 + */ + // public WaitingResponseDto registerWaiting( + // Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto + // ) { + // // Store 유효성 검증 추가 + // Store store = storeRepository.findById(storeId) + // .orElseThrow(StoreNotFoundException::new); + // if (Boolean.FALSE.equals(store.getIsActive())) + // throw new StoreWaitingDisabledException(); + // + // // User Role 검증 추가 + // User user = userRepository.findById(customOAuth2User.getUserId()) + // .orElseThrow(UserNotFoundException::new); + // if (user.getRole() == Role.MANAGER) { + // throw new IllegalArgumentException("Manager cannot register waiting"); + // } + // + // String userId = customOAuth2User.getUserId().toString(); + // long timestamp = System.currentTimeMillis(); + // + // // 예약 신청 유저 큐(queue)에 추가 + // String reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), + // timestamp); + // if (reservationId == null) { + // throw new IllegalStateException("예약 번호 발급 실패"); + // } + // + // // 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환 + // Long rank = waitingUserRedisRepository.getRank(storeId, userId); + // return WaitingResponseDto.builder() + // .reservationNumber(reservationId) + // .rank(rank == null ? -1 : rank.intValue() + 1) + // .partySize(requestDto.getPartySize() == null ? 0 : requestDto.getPartySize()) + // .build(); + // } + public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User principal, + ReservationCreateRequestDto dto) { + + // 0) 스토어/유저 검증 복구 + Store store = storeRepository.findById(storeId).orElseThrow(StoreNotFoundException::new); if (Boolean.FALSE.equals(store.getIsActive())) throw new StoreWaitingDisabledException(); - // User Role 검증 추가 - User user = userRepository.findById(customOAuth2User.getUserId()) - .orElseThrow(UserNotFoundException::new); - if (user.getRole() == Role.MANAGER) { - throw new IllegalArgumentException("Manager cannot register waiting"); + User user = userRepository.findById(principal.getUserId()).orElseThrow(UserNotFoundException::new); + if (user.getRole() == Role.MANAGER) + throw new ReservationAddUnauthorizedException(); + + // (기존 유효성 검사 동일) + String userId = user.getId().toString(); + Duration ttlTo3am = waitingUserRedisRepository.calculateTTLUntilNext03AM(); + + // 1) 이미 해당 store에 대기 중이면 임대 없이 현재 상태 반환 (중복 요청 허용) + if (Boolean.TRUE.equals(waitingUserRedisRepository.isUserWaiting(storeId, userId))) { + Long rank = waitingUserRedisRepository.getRank(storeId, userId); + Integer ps = waitingUserRedisRepository.getPartySize(storeId, userId); + String reservationId = waitingUserRedisRepository.getReservationId(storeId, userId); + return WaitingResponseDto.builder() + .reservationNumber(reservationId) + .rank(rank == null ? -1 : rank.intValue() + 1) + .partySize(ps == null ? 0 : ps) + .build(); } - String userId = customOAuth2User.getUserId().toString(); - long timestamp = System.currentTimeMillis(); - - // 예약 신청 유저 큐(queue)에 추가 - String reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), - timestamp); - if (reservationId == null) { - throw new IllegalStateException("예약 번호 발급 실패"); + // 1) 임대 획득 + String token = java.util.UUID.randomUUID().toString(); + int attempts = 0; + while (true) { + boolean ok = waitingPermitLuaRepository.acquireLease(userId, token, System.currentTimeMillis(), LEASE_MS, + USER_LIMIT, ttlTo3am); + if (ok) + break; + if (++attempts >= 3) + throw new UserWaitingLimitExceededException(); + try { + Thread.sleep((long)(5 * Math.pow(3, attempts - 1))); + } catch (InterruptedException ignored) { + } } - // 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환 - Long rank = waitingUserRedisRepository.getRank(storeId, userId); - return WaitingResponseDto.builder() - .reservationNumber(reservationId) - .rank(rank == null ? -1 : rank.intValue() + 1) - .partySize(requestDto.getPartySize() == null ? 0 : requestDto.getPartySize()) - .build(); + String reservationId = null; + try { + // 2) 스토어 큐 등록(기존 메서드 그대로) + long ts = System.currentTimeMillis(); + reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, dto.getPartySize(), ts); + if (reservationId == null) + throw new ReservationNumberIssueFailException(); + + // 3) 확정(holding→active) + waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), reservationId, ttlTo3am); + + // 4) 응답 + Long rank = waitingUserRedisRepository.getRank(storeId, userId); + return WaitingResponseDto.builder() + .reservationNumber(reservationId) + .rank(rank == null ? -1 : rank.intValue() + 1) + .partySize(dto.getPartySize() == null ? 0 : dto.getPartySize()) + .build(); + + } catch (RuntimeException e) { + // 실패 시 임대 반납 + waitingPermitLuaRepository.releaseLease(userId, token); + throw e; + } } public WaitingResponseDto myWaitingInfo(Long storeId, CustomOAuth2User customOAuth2User) { @@ -140,18 +219,28 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { reservationRepository.save(reservation); - return removed; + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); + return true; + // return removed; } //TODO 성능 개선 필요 public List getAllMyWaitings(CustomOAuth2User customOAuth2User) { String userId = customOAuth2User.getUserId().toString(); - // 1) 현재 SCAN 기반으로 얻어온 storeId 리스트 - List storeIds = waitingUserRedisRepository.getUserWaitingStoreIds(userId); - if (storeIds.isEmpty()) + Set members = waitingPermitLuaRepository.getActiveMembers(userId); + if (members.isEmpty()) return Collections.emptyList(); + // 1) 현재 SCAN 기반으로 얻어온 storeId 리스트 + // List storeIds = waitingUserRedisRepository.getUserWaitingStoreIds(userId); + // if (storeIds.isEmpty()) + // return Collections.emptyList(); + List storeIds = members.stream() + .map(m -> Long.parseLong(m.substring(0, m.indexOf(':')))) + .distinct() + .toList(); + // 2) Store, Department 배치 조회 List stores = storeRepository.findAllWithDepartmentByStoreIdIn(storeIds); Map storeMap = stores.stream() From 4319eff89851acbaa3e274fd4c2541776a1cf7bd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 20:09:33 +0900 Subject: [PATCH 475/685] =?UTF-8?q?test(Reservation):=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=ED=8C=85=20=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=9C=A0=EB=8B=9B=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationServiceConcurrencyTest.java | 54 ++++-- .../service/ReservationServiceTest.java | 181 ++++++++++++++---- 2 files changed, 177 insertions(+), 58 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java index fd7b3614..2ae1d641 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java @@ -26,6 +26,7 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.Role; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; @@ -50,14 +51,15 @@ class ReservationServiceConcurrencyTest { static ReservationService reservationService; // Mockito로 대체할 의존성들 - static ReservationRepository reservationRepo; - static StoreRepository storeRepo; - static UserRepository userRepo; - static DepartmentRepository deptRepo; - static StoreImageRepository storeImageRepo; + static ReservationRepository reservationRepo; + static StoreRepository storeRepo; + static UserRepository userRepo; + static DepartmentRepository deptRepo; + static StoreImageRepository storeImageRepo; + static WaitingPermitLuaRepository waitingPermitRepo; - private static final Long STORE_ID = 100L; - private static final int THREAD_COUNT = 50; + private static final Long STORE_ID = 100L; + private static final int THREAD_COUNT = 50; @BeforeAll static void setupAll() { @@ -71,22 +73,27 @@ static void setupAll() { // 2) 실제 Redis 리포지토리 객체 생성 waitingRepo = new WaitingUserRedisRepository(redisTemplate); + waitingPermitRepo = new WaitingPermitLuaRepository(redisTemplate); // 3) Mockito mock 인스턴스 생성 reservationRepo = mock(ReservationRepository.class); - storeRepo = mock(StoreRepository.class); - userRepo = mock(UserRepository.class); - deptRepo = mock(DepartmentRepository.class); - storeImageRepo = mock(StoreImageRepository.class); + storeRepo = mock(StoreRepository.class); + userRepo = mock(UserRepository.class); + deptRepo = mock(DepartmentRepository.class); + storeImageRepo = mock(StoreImageRepository.class); // 4) store/user 유효성 검증 스텁 Store mockStore = mock(Store.class); when(mockStore.getIsActive()).thenReturn(true); when(storeRepo.findById(anyLong())).thenReturn(Optional.of(mockStore)); - User mockUser = mock(User.class); - when(mockUser.getRole()).thenReturn(Role.USER); - when(userRepo.findById(anyLong())).thenReturn(Optional.of(mockUser)); + when(userRepo.findById(anyLong())).thenAnswer(inv -> { + Long uid = inv.getArgument(0, Long.class); + User u = mock(User.class); + when(u.getRole()).thenReturn(Role.USER); + when(u.getId()).thenReturn(uid); + return Optional.of(u); + }); // 5) ReservationService 실체 생성 reservationService = new ReservationService( @@ -96,6 +103,7 @@ static void setupAll() { waitingRepo, deptRepo, storeImageRepo, + waitingPermitRepo, redisTemplate ); } @@ -103,11 +111,14 @@ static void setupAll() { @BeforeEach void clearRedis() { // 매 테스트마다 Redis 키 초기화 - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*")); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*")); redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + "*")); - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingStatusKeyPrefix() + "*")); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingStatusKeyPrefix() + "*")); redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationNumberKey(STORE_ID) + ":*")); + redisTemplate.delete(redisTemplate.keys("waiting:user:*")); + redisTemplate.delete(RedisKeyUtils.buildReservationNumberKey(STORE_ID)); + redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); } @Test @@ -115,11 +126,12 @@ void clearRedis() { void concurrentRegisterWaiting() throws InterruptedException { // --- Given --- // 50개 스레드를 준비하고, 동시에 시작/종료를 제어할 CountDownLatch - ExecutorService exec = Executors.newFixedThreadPool(THREAD_COUNT); - CountDownLatch startLatch = new CountDownLatch(1); + ExecutorService exec = Executors.newFixedThreadPool(THREAD_COUNT); + CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch finishLatch = new CountDownLatch(THREAD_COUNT); // 결과를 수집할 스레드 안전 리스트 List responses = Collections.synchronizedList(new ArrayList<>()); + List errors = Collections.synchronizedList(new ArrayList<>()); // THREAD_COUNT개의 작업을 스레드풀에 제출 for (int i = 0; i < THREAD_COUNT; i++) { @@ -139,9 +151,8 @@ void concurrentRegisterWaiting() throws InterruptedException { .build(); WaitingResponseDto responseDto = reservationService.registerWaiting(STORE_ID, user, dto); responses.add(responseDto); - - } catch (Exception e) { - fail("예외 발생: " + e.getMessage()); + } catch (Throwable t) { + errors.add(t); } finally { // 작업 완료 신고 finishLatch.countDown(); @@ -158,6 +169,7 @@ void concurrentRegisterWaiting() throws InterruptedException { // --- Then --- // 1) 모든 스레드가 제시간에 완료되었는가? assertTrue(completedInTime, "스레드가 제시간에 완료되지 않았습니다."); + assertTrue(errors.isEmpty(), "스레드 예외 발생: " + errors); // 2) 반환된 DTO 개수가 50개인가? assertEquals(THREAD_COUNT, responses.size(), "전체 응답 수 불일치"); diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java index 36b7deb7..0389a72f 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java @@ -1,8 +1,10 @@ package com.nowait.applicationuser.reservation.service; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.util.Collections; @@ -15,19 +17,24 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.RedisTemplate; +import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; +import com.nowait.domaincorerdb.store.repository.StoreImageRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -41,38 +48,13 @@ public class ReservationServiceTest { @Mock private UserRepository userRepository; @Mock private WaitingUserRedisRepository waitingRepo; @Mock private ReservationRepository reservationRepository; - @InjectMocks private ReservationService service; - - - @Test - @DisplayName("registerWaiting: 성공 시 WaitingResponseDto 반환") - void registerWaiting_Success() { - // Given - Long storeId = 10L; - CustomOAuth2User user = mock(CustomOAuth2User.class); - when(user.getUserId()).thenReturn(100L); - ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(2).build(); - Store store = mock(Store.class); - when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); - when(store.getIsActive()).thenReturn(true); - User domainUser = mock(User.class); - when(userRepository.findById(100L)).thenReturn(Optional.of(domainUser)); - when(domainUser.getRole()).thenReturn(Role.USER); - when(waitingRepo.addToWaitingQueue(eq(storeId), eq("100"), eq(2), anyLong())) - .thenReturn("10-20250804-0001"); - when(waitingRepo.getRank(storeId, "100")).thenReturn(4L); + @Mock private WaitingPermitLuaRepository waitingPermitLuaRepository; + @Mock private DepartmentRepository departmentRepository; + @Mock private StoreImageRepository storeImageRepository; + @Mock private RedisTemplate redisTemplate; - // When - WaitingResponseDto result = service.registerWaiting(storeId, user, dto); + @InjectMocks private ReservationService service; - // Then - assertNotNull(result); - assertEquals("10-20250804-0001", result.getReservationNumber()); - assertEquals(5, result.getRank()); - assertEquals(2, result.getPartySize()); - // Redis 등록 호출 검증 - verify(waitingRepo).addToWaitingQueue(eq(storeId), eq("100"), eq(2), anyLong()); - } @Test @DisplayName("registerWaiting: 스토어 없음 예외") @@ -129,18 +111,26 @@ void cancelWaiting_Success() { } @Test - @DisplayName("getAllMyWaitings: 대기 없음 시 빈 리스트 반환") - void getAllMyWaitings_Empty() { + @DisplayName("getAllMyWaitings: active가 비어 있으면 빈 리스트 반환") + void getAllMyWaitings_Empty_GWT() { // Given - CustomOAuth2User user = mock(CustomOAuth2User.class); - when(user.getUserId()).thenReturn(40L); - when(waitingRepo.getUserWaitingStoreIds("40")).thenReturn(Collections.emptyList()); + CustomOAuth2User principal = mock(CustomOAuth2User.class); + when(principal.getUserId()).thenReturn(100L); + + when(waitingPermitLuaRepository.getActiveMembers("100")) + .thenReturn(Collections.emptySet()); // When - List list = service.getAllMyWaitings(user); + List result = service.getAllMyWaitings(principal); // Then - assertTrue(list.isEmpty()); + assertThat(result).isEmpty(); + + // 불필요한 스텁/호출이 없도록 보장 + verify(waitingPermitLuaRepository).getActiveMembers("100"); + verifyNoMoreInteractions(waitingPermitLuaRepository); + // storeRepository 등은 호출되지 않아야 함 + verifyNoInteractions(storeRepository, departmentRepository, storeImageRepository, waitingRepo); } @@ -255,4 +245,121 @@ void create_StoreDisabledException() { ) ); } + + @Test + @DisplayName("registerWaiting: 성공 시 WaitingResponseDto 반환") + void registerWaiting_Success_GWT() { + // Given + Long storeId = 10L; + CustomOAuth2User principal = mock(CustomOAuth2User.class); + when(principal.getUserId()).thenReturn(100L); + + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(2).build(); + + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(true); + + User domainUser = mock(User.class); + when(userRepository.findById(100L)).thenReturn(Optional.of(domainUser)); + when(domainUser.getRole()).thenReturn(Role.USER); + // 서비스가 user.getId()를 쓰는 경우 필수 + when(domainUser.getId()).thenReturn(100L); + + when(waitingRepo.calculateTTLUntilNext03AM()).thenReturn(Duration.ofHours(1)); + when(waitingRepo.isUserWaiting(storeId, "100")).thenReturn(false); + when(waitingPermitLuaRepository.acquireLease(eq("100"), anyString(), anyLong(), anyLong(), eq(3), any())) + .thenReturn(true); + + when(waitingRepo.addToWaitingQueue(eq(storeId), eq("100"), eq(2), anyLong())) + .thenReturn("10-20250804-0001"); + when(waitingRepo.getRank(storeId, "100")).thenReturn(4L); + + // When + WaitingResponseDto result = service.registerWaiting(storeId, principal, dto); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getReservationNumber()).isEqualTo("10-20250804-0001"); + assertThat(result.getRank()).isEqualTo(5); + assertThat(result.getPartySize()).isEqualTo(2); + + verify(waitingPermitLuaRepository).acquireLease(eq("100"), anyString(), anyLong(), anyLong(), eq(3), any()); + verify(waitingRepo).addToWaitingQueue(eq(storeId), eq("100"), eq(2), anyLong()); + verify(waitingPermitLuaRepository).finalizeActive(eq("100"), anyString(), eq(String.valueOf(storeId)), eq("10-20250804-0001"), any()); + verifyNoMoreInteractions(waitingPermitLuaRepository, waitingRepo); + } + + + @Test + @DisplayName("registerWaiting: 동일 매장 재요청 시 기존 정보 반환(임대 미소비)") + void registerWaiting_Duplicate_SameStore_GWT() { + // Given + Long storeId = 10L; + CustomOAuth2User principal = mock(CustomOAuth2User.class); + when(principal.getUserId()).thenReturn(100L); + + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(2).build(); + + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(true); + + User domainUser = mock(User.class); + when(userRepository.findById(100L)).thenReturn(Optional.of(domainUser)); + when(domainUser.getRole()).thenReturn(Role.USER); + when(domainUser.getId()).thenReturn(100L); + + // 이미 해당 매장에 있는 상태 + when(waitingRepo.isUserWaiting(storeId, "100")).thenReturn(true); + when(waitingRepo.getRank(storeId, "100")).thenReturn(4L); + when(waitingRepo.getPartySize(storeId, "100")).thenReturn(2); + when(waitingRepo.getReservationId(storeId, "100")).thenReturn("10-20250804-0001"); + + // When + WaitingResponseDto result = service.registerWaiting(storeId, principal, dto); + + // Then + assertThat(result.getReservationNumber()).isEqualTo("10-20250804-0001"); + assertThat(result.getRank()).isEqualTo(5); + assertThat(result.getPartySize()).isEqualTo(2); + + verify(waitingPermitLuaRepository, never()).acquireLease(any(), any(), anyLong(), anyLong(), anyInt(), any()); + verify(waitingRepo, never()).addToWaitingQueue(anyLong(), anyString(), anyInt(), anyLong()); + } + + + @Test + @DisplayName("registerWaiting: 유저 한도(3개) 초과 시 예외") + void registerWaiting_LimitExceeded_GWT() { + // Given + Long storeId = 40L; + CustomOAuth2User principal = mock(CustomOAuth2User.class); + when(principal.getUserId()).thenReturn(100L); + + ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder().partySize(2).build(); + + Store store = mock(Store.class); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(store.getIsActive()).thenReturn(true); + + User domainUser = mock(User.class); + when(userRepository.findById(100L)).thenReturn(Optional.of(domainUser)); + when(domainUser.getRole()).thenReturn(Role.USER); + when(domainUser.getId()).thenReturn(100L); + + when(waitingRepo.calculateTTLUntilNext03AM()).thenReturn(Duration.ofHours(1)); + when(waitingRepo.isUserWaiting(storeId, "100")).thenReturn(false); + // 임대 3회 모두 실패하도록 + when(waitingPermitLuaRepository.acquireLease(eq("100"), anyString(), anyLong(), anyLong(), eq(3), any())) + .thenReturn(false); + + // When & Then + assertThatThrownBy(() -> service.registerWaiting(storeId, principal, dto)) + .isInstanceOf(com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException.class) + .hasMessageContaining("유저당 웨이팅 가능 개수"); + + verify(waitingRepo, never()).addToWaitingQueue(anyLong(), anyString(), anyInt(), anyLong()); + verify(waitingPermitLuaRepository, atLeastOnce()).acquireLease(eq("100"), anyString(), anyLong(), anyLong(), eq(3), any()); + } } From c320e9237df66a4ba09b64d72f6035e0a0608e07 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 21:02:00 +0900 Subject: [PATCH 476/685] =?UTF-8?q?bug(Reservation):=20=EC=9C=A0=EB=A0=B9?= =?UTF-8?q?=20=ED=95=AD=EB=AA=A9=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 22 +++++++++++++++- .../reservation/ReservationServiceTest.java | 1 - .../WaitingRedisRepositoryTest.java | 3 --- .../service/ReservationService.java | 25 +++++++++++++++++-- .../ReservationServiceConcurrencyTest.java | 2 +- .../service/ReservationServiceTest.java | 1 - .../WaitingPermitLuaRepositoryTest.java | 1 - .../WaitingPermitLuaRepository.java | 2 +- .../repository/WaitingRedisRepository.java | 3 +-- 9 files changed, 47 insertions(+), 13 deletions(-) rename {nowait-app-user-api/src/main/java/com/nowait/applicationuser => nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis}/reservation/repository/WaitingPermitLuaRepository.java (98%) rename {nowait-app-admin-api/src/main/java/com/nowait/applicationadmin => nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis}/reservation/repository/WaitingRedisRepository.java (97%) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index b2714127..63139bbf 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -24,7 +24,6 @@ import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; -import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.reservation.entity.Reservation; @@ -38,6 +37,8 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; +import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; import lombok.RequiredArgsConstructor; @@ -49,6 +50,7 @@ public class ReservationService { private final UserRepository userRepository; private final WaitingRedisRepository waitingRedisRepository; private final StoreRepository storeRepository; + private final WaitingPermitLuaRepository waitingPermitLuaRepository; private final RedisTemplate redisTemplate; //TODO 성능 비교를 위해 남겨둔 로직 @@ -242,6 +244,12 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name() .equals(currStatus)) { + if (reservationNumber != null) { + waitingPermitLuaRepository.removeActiveMember( + userId, String.valueOf(storeId), reservationNumber + ); + } + // 새 Reservation 생성 & 저장 Reservation r = Reservation.builder() .reservationNumber(reservationNumber) @@ -259,6 +267,12 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me waitingRedisRepository.deleteWaiting(storeId, userId); return EntryStatusResponseDto.fromEntity(saved); } else { + if (reservationNumber != null) { + try { + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); + } catch (Exception ignore) {} + } + // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 // TODO 메서드로 분리 LocalDateTime start = LocalDate.now().atStartOfDay(); @@ -283,6 +297,12 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me throw new IllegalStateException("WAITING/CALLING 상태에서만 취소 가능합니다."); } + if (reservationNumber != null) { + waitingPermitLuaRepository.removeActiveMember( + userId, String.valueOf(storeId), reservationNumber + ); + } + Reservation r = Reservation.builder() .reservationNumber(reservationNumber) .store(storeRepository.getReferenceById(storeId)) diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java index c3757ca5..2290d86c 100644 --- a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java @@ -26,7 +26,6 @@ import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; -import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; import com.nowait.applicationadmin.reservation.service.ReservationService; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java index 841c1ea7..0b63cad2 100644 --- a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java @@ -1,6 +1,5 @@ package com.nowait.applicationadmin.reservation; -import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import org.junit.jupiter.api.BeforeEach; @@ -13,8 +12,6 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ZSetOperations; -import com.nowait.applicationadmin.reservation.repository.WaitingRedisRepository; - public class WaitingRedisRepositoryTest { @Mock private StringRedisTemplate template; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 69c5956a..82e66e3c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -22,7 +22,6 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; -import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; @@ -45,6 +44,7 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import lombok.RequiredArgsConstructor; @@ -218,7 +218,6 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { .build(); reservationRepository.save(reservation); - waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); return true; // return removed; @@ -232,6 +231,11 @@ public List getAllMyWaitings(CustomOAuth2User customOAuth2Use if (members.isEmpty()) return Collections.emptyList(); + Map activeResIdByStore = members.stream() + .map(m -> m.split(":", 2)) + .filter(a -> a.length == 2) + .collect(Collectors.toMap(a -> Long.parseLong(a[0]), a -> a[1], (a, b) -> a)); + // 1) 현재 SCAN 기반으로 얻어온 storeId 리스트 // List storeIds = waitingUserRedisRepository.getUserWaitingStoreIds(userId); // if (storeIds.isEmpty()) @@ -298,6 +302,23 @@ public List getAllMyWaitings(CustomOAuth2User customOAuth2Use String status = (String)it.next(); String reservationId = (String)it.next(); + + String activeReservationId = activeResIdByStore.get(storeId); + // 유령 감지: 큐에 없음/번호 불일치/번호 null + boolean zombie = (rankObj == null) || (reservationId == null) || + (activeReservationId != null && !activeReservationId.equals(reservationId)); + if (zombie) { + try { + String toRemove = (activeReservationId != null) ? activeReservationId + : (reservationId != null ? reservationId : null); + if (toRemove != null) { + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), toRemove); + } + } catch (Exception ignore) {} + continue; // 목록에서 제외 + } + + int rank = (rankObj != null ? rankObj.intValue() + 1 : 0); int teamsAhead = (rankObj != null ? rankObj.intValue() : 0); int partySize = (partyStr != null ? Integer.parseInt(partyStr) : 0); diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java index 2ae1d641..bbba92f8 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java @@ -26,7 +26,6 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; -import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.Role; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; @@ -37,6 +36,7 @@ import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import com.redis.testcontainers.RedisContainer; diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java index 0389a72f..35342205 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java @@ -23,7 +23,6 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; -import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java index 6f22a617..a9ca63ab 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java @@ -14,7 +14,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import com.nowait.applicationuser.reservation.repository.WaitingPermitLuaRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; @Testcontainers diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java similarity index 98% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java rename to nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java index 8804919f..ffab7c60 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingPermitLuaRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.reservation.repository; +package com.nowait.domaincoreredis.reservation.repository; import java.nio.charset.StandardCharsets; import java.time.Duration; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java similarity index 97% rename from nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java rename to nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index 02e29c82..0799786e 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -1,7 +1,6 @@ -package com.nowait.applicationadmin.reservation.repository; +package com.nowait.domaincoreredis.reservation.repository; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.Set; From 1ff38d163453303cdbfafcefd629fef9cb351782 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 21:10:37 +0900 Subject: [PATCH 477/685] =?UTF-8?q?test(Reservation):=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/reservation/ReservationServiceTest.java | 1 + .../reservation/WaitingRedisRepositoryTest.java | 3 +++ .../reservation/service/ReservationServiceTest.java | 1 + .../reservation/service/WaitingPermitLuaRepositoryTest.java | 1 + 4 files changed, 6 insertions(+) diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java index 2290d86c..3c191177 100644 --- a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/ReservationServiceTest.java @@ -37,6 +37,7 @@ import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; public class ReservationServiceTest { diff --git a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java index 0b63cad2..3b399325 100644 --- a/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java +++ b/nowait-app-admin-api/src/test/java/com/nowait/applicationadmin/reservation/WaitingRedisRepositoryTest.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.reservation; +import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import org.junit.jupiter.api.BeforeEach; @@ -12,6 +13,8 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ZSetOperations; +import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; + public class WaitingRedisRepositoryTest { @Mock private StringRedisTemplate template; diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java index 35342205..39697323 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java @@ -38,6 +38,7 @@ import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @ExtendWith(MockitoExtension.class) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java index a9ca63ab..ae32ab4b 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/WaitingPermitLuaRepositoryTest.java @@ -15,6 +15,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; @Testcontainers public class WaitingPermitLuaRepositoryTest { From e90146a3797035606aece20634f3fb85c0cf2270 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 21:12:00 +0900 Subject: [PATCH 478/685] =?UTF-8?q?refactor(Reservation):=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy-admin-docker.sh | 4 ++-- scripts/deploy-user-docker.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 93297fbb..1a19c8fa 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin down echo "3. start container" -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api prometheus-admin grafana-admin -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 46c78a4f..3bf2d6cf 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user down echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api prometheus-user grafana-user -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From feaa47efc710ffd4d4c5a14ed3936008d81e6296 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 12 Aug 2025 21:16:34 +0900 Subject: [PATCH 479/685] =?UTF-8?q?refactor(Reservation):=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 - scripts/deploy-admin-docker.sh | 4 ++-- scripts/deploy-user-docker.sh | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - diff --git a/scripts/deploy-admin-docker.sh b/scripts/deploy-admin-docker.sh index 1a19c8fa..93297fbb 100644 --- a/scripts/deploy-admin-docker.sh +++ b/scripts/deploy-admin-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin down echo "3. start container" -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api -sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin pull nowait-app-admin-api prometheus-admin grafana-admin +sudo docker-compose -f docker-compose.admin.yml -f docker-compose.admin-monitoring.yml -p nowait_dev_admin up -d nowait-app-admin-api prometheus-admin grafana-admin echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-admin-api") diff --git a/scripts/deploy-user-docker.sh b/scripts/deploy-user-docker.sh index 3bf2d6cf..46c78a4f 100644 --- a/scripts/deploy-user-docker.sh +++ b/scripts/deploy-user-docker.sh @@ -22,8 +22,8 @@ echo "Cleaning up old containers…" docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user down echo "3. start container" -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api -sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user pull nowait-app-user-api prometheus-user grafana-user +sudo docker-compose -f docker-compose.user.yml -f docker-compose.user-monitoring.yml -p nowait_dev_user up -d nowait-app-user-api prometheus-user grafana-user echo "4. check container status" NEW_CONTAINER_ID=$(docker ps -q --filter "name=nowait-app-user-api") From db4e572f2fbe17bd3fde1b8cce06956d650a05d7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 15 Aug 2025 14:51:39 +0900 Subject: [PATCH 480/685] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B0=80=EB=8A=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9A=A9=EB=9F=89=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,4 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From ea22b9704bc46b25c5d002f55f5178aa840d4cb0 Mon Sep 17 00:00:00 2001 From: Jihun_Eddie <100821696+Jjiggu@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:06:40 +0900 Subject: [PATCH 481/685] Update README.md From 3b9fb71f52b771185dbd6f5a1b3626c982d7093a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 22 Aug 2025 16:07:25 +0900 Subject: [PATCH 482/685] =?UTF-8?q?refactor:=20storeName=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/menu/dto/MenuReadResponse.java | 4 +++- .../nowait/applicationuser/menu/service/MenuService.java | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java index 5e9c6c21..538479b0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/dto/MenuReadResponse.java @@ -11,10 +11,12 @@ @Builder public class MenuReadResponse { + private String storeName; private List menuReadDto; - public static MenuReadResponse of(List menuReadDto) { + public static MenuReadResponse of(String storeName, List menuReadDto) { return MenuReadResponse.builder() + .storeName(storeName) .menuReadDto(menuReadDto) .build(); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index 2a5c687f..8becf9a6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -14,6 +14,7 @@ import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; @@ -34,9 +35,10 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { throw new MenuParamEmptyException(); } - storeRepository.findById(storeId) + Store store = storeRepository.findById(storeId) .orElseThrow(StoreNotFoundException::new); + String storeName = store.getName(); List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() @@ -49,7 +51,7 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { }) .toList(); - return MenuReadResponse.of(menuReadResponse); + return MenuReadResponse.of(storeName, menuReadResponse); } @Transactional(readOnly = true) From 4afc19c6115d008c69ead5056d29f44dd6d297e8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 17:27:09 +0900 Subject: [PATCH 483/685] =?UTF-8?q?refactor:=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 97111041..909fac3c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -12,6 +12,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.web.cors.CorsConfigurationSource; import com.nowait.applicationuser.oauth.oauth2.CustomOAuth2UserService; @@ -37,7 +38,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.configurationSource(corsConfigurationSource)) // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) - .csrf(AbstractHttpConfigurer::disable) + .csrf(csrf -> csrf + .ignoringRequestMatchers( + "/api/**", "/login/**", "/oauth2/**", + "/swagger-ui/**", "/v3/api-docs/**", "/orders/**") + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + ) // 시큐리티 폼 로그인 비활성화 .formLogin(AbstractHttpConfigurer::disable) // HTTP Basic 인증 비활성화 @@ -50,9 +56,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { userInfoEndpoint.userService(customOAuth2UserService) ).successHandler(oAuth2LoginSuccessHandler) ) - // 세션 사용하지 않음 .sessionManagement(session -> - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) ) .authorizeHttpRequests(auth -> auth .requestMatchers( From 78f5aacae7fb6f8298f28581e75bed4938397e02 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 17:27:25 +0900 Subject: [PATCH 484/685] =?UTF-8?q?refactor:=20=EC=A4=84=EB=B0=94=EA=BF=88?= =?UTF-8?q?=20=EA=B3=B5=EB=B0=B1=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/order/service/OrderService.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 254caac4..656f6846 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -116,11 +116,6 @@ public List getOrderItemsGroupByOrderId( } - - - - - private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { if (storeId == null || tableId == null || orderCreateRequestDto == null) { throw new OrderParameterEmptyException(); From d53f0fdd6640d24c62890c5b965659a8666015e0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 17:27:43 +0900 Subject: [PATCH 485/685] =?UTF-8?q?refactor:=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/order/controller/OrderController.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 8741e075..c814a800 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -55,8 +56,13 @@ public ResponseEntity createOrder( public ResponseEntity getOrderItems( @PathVariable Long storeId, @PathVariable Long tableId, - HttpSession session + HttpServletRequest request ) { + HttpSession session = request.getSession(false); + if (session == null) { + // 프론트가 먼저 부트스트랩 안 했거나, 쿠키가 안 붙은 케이스 + return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.success(List.of())); + } String sessionId = session.getId(); List orderItems = orderService.getOrderItemsGroupByOrderId(storeId, tableId, sessionId); return ResponseEntity. From a62e39eb65aa52399a812ab1d1565e55eb2d084d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 17:27:51 +0900 Subject: [PATCH 486/685] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/menu/controller/MenuImageController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java index 5d6add61..9d096bc3 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/controller/MenuImageController.java @@ -65,8 +65,8 @@ public ResponseEntity uploadMenuImage( description = "특정 메뉴 이미지 ID에 해당하는 이미지를 삭제합니다." ) @ApiResponse(responseCode = "200", description = "메뉴 이미지 삭제 성공") - public ResponseEntity deleteMenuImage(@PathVariable Long id) { - menuImageService.delete(id); + public ResponseEntity deleteMenuImage(@PathVariable Long menuImageId) { + menuImageService.delete(menuImageId); return ResponseEntity .status( HttpStatus.OK From 57c1e661017223edcf55a989f31008433f205605 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 17:28:00 +0900 Subject: [PATCH 487/685] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index b89e7b7c..cf844f46 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -19,10 +19,9 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 - config.setAllowCredentials(true); // 쿠키 포함 허용 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); //** 뜻은 모든 URL 경로에 적용한다는 의미 + source.registerCorsConfiguration("/**", config); // 모든 URL 경로에 적용한다는 의미 return source; } } From 655d3ccc6e91cc3535b5de1116729acd71695dd8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 18:52:10 +0900 Subject: [PATCH 488/685] =?UTF-8?q?feat:=20pathVariable=20publicCode?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 12 +++---- .../order/controller/OrderController.java | 33 +++++++++++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java index b9abf632..9c8b997a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java @@ -25,32 +25,32 @@ public class MenuController { private final MenuService menuService; - @GetMapping("/all-menus/stores/{storeId}") + @GetMapping("/all-menus/stores/{publicCode}") @Operation(summary = "가게의 모든 메뉴 조회", description = "특정 가게의 모든 메뉴를 조회합니다.") @ApiResponse(responseCode = "200", description = "모든 메뉴를 조회 성공") - public ResponseEntity getMenusByStoreId(@PathVariable Long storeId) { + public ResponseEntity getMenusByStoreId(@PathVariable String publicCode) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.getAllMenusByStoreId(storeId) + menuService.getAllMenusByStoreId(publicCode) ) ); } - @GetMapping("/{storeId}/{menuId}") + @GetMapping("/{publicCode}/{menuId}") @Operation( summary = "메뉴 ID로 메뉴 조회", description = "특정 가게의 특정 메뉴를 ID로 조회합니다.") @ApiResponse(responseCode = "200", description = "메뉴 조회 성공") public ResponseEntity getMenuById( - @PathVariable Long storeId, + @PathVariable String publicCode, @PathVariable Long menuId ) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - menuService.getMenuById(storeId, menuId) + menuService.getMenuById(publicCode, menuId) ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index c814a800..76501d55 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -32,17 +32,36 @@ public class OrderController { private final OrderService orderService; - @PostMapping("/create/{storeId}/{tableId}") + // @PostMapping("/create/{storeId}/{tableId}") + // @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") + // @ApiResponse(responseCode = "201", description = "주문 생성") + // public ResponseEntity createOrder( + // @PathVariable Long storeId, + // @PathVariable Long tableId, + // @RequestBody @Valid OrderCreateRequestDto orderCreateRequestDto, + // HttpSession session + // ) { + // String sessionId = session.getId(); + // OrderCreateResponseDto response = orderService.createOrder(storeId, tableId, orderCreateRequestDto, sessionId); + // return ResponseEntity + // .status(HttpStatus.CREATED) + // .body( + // ApiUtils.success(response) + // ); + // } + + @PostMapping("/create/{publicCode}/{tableId}") @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") @ApiResponse(responseCode = "201", description = "주문 생성") public ResponseEntity createOrder( - @PathVariable Long storeId, + @PathVariable String publicCode, @PathVariable Long tableId, @RequestBody @Valid OrderCreateRequestDto orderCreateRequestDto, HttpSession session - ) { + ) { String sessionId = session.getId(); - OrderCreateResponseDto response = orderService.createOrder(storeId,tableId,orderCreateRequestDto,sessionId); + OrderCreateResponseDto response = orderService.createOrder(publicCode, tableId, orderCreateRequestDto, + sessionId); return ResponseEntity .status(HttpStatus.CREATED) .body( @@ -50,11 +69,11 @@ public ResponseEntity createOrder( ); } - @GetMapping("/items/{storeId}/{tableId}") + @GetMapping("/items/{publicCode}/{tableId}") @Operation(summary = "테이블별 주문 아이템 조회", description = "비로그인(세션) 기준으로 테이블의 내 주문 목록만 조회") @ApiResponse(responseCode = "200", description = "주문 조회") public ResponseEntity getOrderItems( - @PathVariable Long storeId, + @PathVariable String publicCode, @PathVariable Long tableId, HttpServletRequest request ) { @@ -64,7 +83,7 @@ public ResponseEntity getOrderItems( return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.success(List.of())); } String sessionId = session.getId(); - List orderItems = orderService.getOrderItemsGroupByOrderId(storeId, tableId, sessionId); + List orderItems = orderService.getOrderItemsGroupByOrderId(publicCode, tableId, sessionId); return ResponseEntity. status(HttpStatus.OK) .body( From 5bb4a39db1383146964903d19e9dc41442a66b80 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 18:52:35 +0900 Subject: [PATCH 489/685] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20storeId=20?= =?UTF-8?q?publicCode=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/service/MenuService.java | 15 ++++++++------- .../order/service/OrderService.java | 18 +++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index 8becf9a6..45aa92e5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -30,15 +30,16 @@ public class MenuService { @Transactional(readOnly = true) - public MenuReadResponse getAllMenusByStoreId(Long storeId) { - if (storeId == null) { + public MenuReadResponse getAllMenusByStoreId(String publicCode) { + if (publicCode == null) { throw new MenuParamEmptyException(); } - Store store = storeRepository.findById(storeId) + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); String storeName = store.getName(); + Long storeId = store.getStoreId(); List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() @@ -55,15 +56,15 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) { } @Transactional(readOnly = true) - public MenuReadDto getMenuById(Long storeId, Long menuId) { - if (storeId == null || menuId == null) { + public MenuReadDto getMenuById(String publicCode, Long menuId) { + if (publicCode == null || menuId == null) { throw new MenuParamEmptyException(); } - storeRepository.findById(storeId) + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); - Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) + Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(store.getStoreId(), menuId) .orElseThrow(MenuNotFoundException::new); List images = menuImageRepository.findByMenu(menu); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 656f6846..5b317dcc 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -39,16 +39,16 @@ public class OrderService { private final MenuRepository menuRepository; private final OrderItemRepository orderItemRepository; @Transactional - public OrderCreateResponseDto createOrder(Long storeId, Long tableId, + public OrderCreateResponseDto createOrder(String publicCode, Long tableId, OrderCreateRequestDto orderCreateRequestDto, String sessionId) { - parameterValidation(storeId, tableId, orderCreateRequestDto); + parameterValidation(publicCode, tableId, orderCreateRequestDto); // 💡 [중복 주문 방지] signature 생성 및 체크 - String signature = generateOrderSignature(storeId, tableId, orderCreateRequestDto.getItems()); + String signature = generateOrderSignature(publicCode, tableId, orderCreateRequestDto.getItems()); checkDuplicateOrderSignature(signature); // 1. Store 조회 - Store store = storeRepository.findById(storeId) + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(() -> new IllegalArgumentException("store not found")); // 2. UserOrder 생성 및 signature 저장 @@ -95,9 +95,9 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId, @Transactional(readOnly = true) public List getOrderItemsGroupByOrderId( - Long storeId, Long tableId, String sessionId) { + String publicCode, Long tableId, String sessionId) { - List userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId); + List userOrders = orderRepository.findByStore_PublicCodeAndTableIdAndSessionId(publicCode, tableId, sessionId); // orderId 기준으로 바로 변환 return userOrders.stream() @@ -116,8 +116,8 @@ public List getOrderItemsGroupByOrderId( } - private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { - if (storeId == null || tableId == null || orderCreateRequestDto == null) { + private static void parameterValidation(String publicCode, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { + if (publicCode == null || tableId == null || orderCreateRequestDto == null) { throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getItems() == null || orderCreateRequestDto.getItems().isEmpty()) { @@ -130,7 +130,7 @@ private static void parameterValidation(Long storeId, Long tableId, OrderCreateR throw new IllegalArgumentException("Depositor name is too long"); } } - private String generateOrderSignature(Long storeId, Long tableId, List items) { + private String generateOrderSignature(String storeId, Long tableId, List items) { String cartString = items.stream() .sorted((a, b) -> a.getMenuId().compareTo(b.getMenuId())) // 메뉴 ID 기준 정렬 .map(item -> item.getMenuId() + ":" + item.getQuantity()) From 844ec2df25d0c30f50d40f7951c751ab7c0c9211 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 18:53:00 +0900 Subject: [PATCH 490/685] =?UTF-8?q?refactor:=20publicCode=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/store/dto/StoreCreateRequest.java | 2 ++ .../applicationadmin/store/dto/StoreCreateResponse.java | 2 ++ .../com/nowait/domaincorerdb/store/entity/Store.java | 9 +++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java index 7650a985..5eff72bb 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateRequest.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.store.dto; +import com.nowait.common.token.TokenGenerator; import com.nowait.domaincorerdb.store.entity.Store; import jakarta.validation.constraints.NotBlank; @@ -25,6 +26,7 @@ public class StoreCreateRequest { public Store toEntity() { return Store.builder() + .publicCode(TokenGenerator.base62(12)) .departmentId(departmentId) .name(name) .location(location) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java index 725c3159..6dcaf79d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/dto/StoreCreateResponse.java @@ -14,6 +14,7 @@ public class StoreCreateResponse { private Long storeId; + private String publicCode; private Long departmentId; private String name; private String location; @@ -29,6 +30,7 @@ public static StoreCreateResponse fromEntity(Store store) { return StoreCreateResponse.builder() .createdAt(store.getCreatedAt()) .storeId(store.getStoreId()) + .publicCode(store.getPublicCode()) .departmentId(store.getDepartmentId()) .name(store.getName()) .location(store.getLocation()) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java index 5aa58b95..a635dab2 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java @@ -28,6 +28,9 @@ public class Store extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long storeId; + @Column(nullable = false, unique = true, updatable = false, length = 12) + private String publicCode; + @Column(name = "department_id", nullable = false) private Long departmentId; @@ -56,7 +59,8 @@ public class Store extends BaseTimeEntity { private Boolean deleted; public Store(LocalDateTime createdAt, Long storeId, Long departmentId, String name, String location, - String description, String noticeTitle, String noticeContent, String openTime, Boolean isActive, Boolean deleted) { + String description, String noticeTitle, String noticeContent, String openTime, Boolean isActive, + Boolean deleted) { super(createdAt); this.storeId = storeId; this.departmentId = departmentId; @@ -70,7 +74,8 @@ public Store(LocalDateTime createdAt, Long storeId, Long departmentId, String na this.deleted = deleted; } - public void updateInfo(String name, String location, String description, String noticeTitle, String notice, String openTime) { + public void updateInfo(String name, String location, String description, String noticeTitle, String notice, + String openTime) { if (name != null) this.name = name; if (location != null) From 3bcfd65814df935274c21dc88710e8533a0dd05a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 18:53:16 +0900 Subject: [PATCH 491/685] =?UTF-8?q?feat:=20findByPublicCodeAndDeletedFalse?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/domaincorerdb/store/repository/StoreRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java index 921a0ad2..83d7d324 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java @@ -18,6 +18,8 @@ public interface StoreRepository extends JpaRepository, StoreCustom Optional findByStoreIdAndDeletedFalse(Long storeId); + Optional findByPublicCodeAndDeletedFalse(String publicCode); + Slice findAllByDeletedFalseOrderByStoreIdAsc(Pageable pageable); @Query(value = """ From 5f48a2a9c95f59fd2c36c0f8b75a64298e6da491 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 18:53:30 +0900 Subject: [PATCH 492/685] =?UTF-8?q?refactor:=20findByStore=5FPublicCodeAnd?= =?UTF-8?q?TableIdAndSessionId=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/domaincorerdb/order/repository/OrderRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index b1a38e8e..d5bb6bf4 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -13,7 +13,7 @@ public interface OrderRepository extends JpaRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); - List findByStore_StoreIdAndTableIdAndSessionId(Long storeId, Long tableId, String sessionId); + List findByStore_PublicCodeAndTableIdAndSessionId(String publicCode, Long tableId, String sessionId); @EntityGraph(attributePaths = {"orderItems", "orderItems.menu"}) List findAllByStore_StoreIdAndCreatedAtBetween(Long storeId, LocalDateTime startDateTime, LocalDateTime endDateTime); From 3cfd5730dd7afde3d04f1d6220e03e9580c79357 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 18:53:43 +0900 Subject: [PATCH 493/685] =?UTF-8?q?feat:=20=ED=86=A0=ED=81=B0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/common/token/TokenGenerator.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 nowait-common/src/main/java/com/nowait/common/token/TokenGenerator.java diff --git a/nowait-common/src/main/java/com/nowait/common/token/TokenGenerator.java b/nowait-common/src/main/java/com/nowait/common/token/TokenGenerator.java new file mode 100644 index 00000000..2b84840b --- /dev/null +++ b/nowait-common/src/main/java/com/nowait/common/token/TokenGenerator.java @@ -0,0 +1,17 @@ +package com.nowait.common.token; + +import java.security.SecureRandom; + +public final class TokenGenerator { + private static final SecureRandom RNG = new SecureRandom(); + private static final char[] ALPHABET = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); + + // 12~16 권장 + public static String base62(int len) { + char[] out = new char[len]; + for (int i = 0; i < len; i++) out[i] = ALPHABET[RNG.nextInt(ALPHABET.length)]; + return new String(out); + } + private TokenGenerator() {} +} From 390a43400afa332894e36b9f7ab99ebd33f9dbc9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 26 Aug 2025 19:13:39 +0900 Subject: [PATCH 494/685] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20storeI?= =?UTF-8?q?d=20publicCode=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 6 +++--- .../storepayment/service/StorePaymentService.java | 2 +- .../service/StorePaymentServiceImpl.java | 13 +++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index 12901d64..c8cd09c4 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -28,11 +28,11 @@ public class StorePaymentController { private final StorePaymentService storePaymentService; - @GetMapping(("/{storeId}")) + @GetMapping(("/{publicCode}")) @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") - public ResponseEntity getStorePaymentByStoreId(@PathVariable Long storeId) { - Optional response = storePaymentService.getStorePaymentByStoreId(storeId); + public ResponseEntity getStorePaymentByStoreId(@PathVariable String publicCode) { + Optional response = storePaymentService.getStorePaymentByStoreId(publicCode); if (response.isPresent()) { return ResponseEntity diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java index 703b8df7..100b6dac 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentService.java @@ -5,5 +5,5 @@ import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; public interface StorePaymentService { - Optional getStorePaymentByStoreId(Long storeId); + Optional getStorePaymentByStoreId(String publicCode); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java index 5e15b7ff..c4c9c295 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/service/StorePaymentServiceImpl.java @@ -5,6 +5,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.storepayment.dto.StorePaymentReadDto; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; import com.nowait.domaincorerdb.storepayment.repository.StorePaymentRepository; @@ -15,13 +18,15 @@ public class StorePaymentServiceImpl implements StorePaymentService { private final StorePaymentRepository storePaymentRepository; + private final StoreRepository storeRepository; @Override @Transactional(readOnly = true) - public Optional getStorePaymentByStoreId(Long storeId) { - if (storeId == null) throw new StorePaymentParamEmptyException(); - - return storePaymentRepository.findByStoreId(storeId) + public Optional getStorePaymentByStoreId(String publicCode) { + if (publicCode == null) throw new StorePaymentParamEmptyException(); + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) + .orElseThrow(StoreNotFoundException::new); + return storePaymentRepository.findByStoreId(store.getStoreId()) .map(StorePaymentReadDto::fromEntity); } } From 8ff156b5dc1d9a4c63cdcd4186548a7e5b81c732 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 27 Aug 2025 21:24:05 +0900 Subject: [PATCH 495/685] =?UTF-8?q?bug:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20cors=20=EB=B2=84=EA=B7=B8=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 909fac3c..17ab83f4 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -12,7 +12,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.web.cors.CorsConfigurationSource; import com.nowait.applicationuser.oauth.oauth2.CustomOAuth2UserService; @@ -38,12 +37,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.configurationSource(corsConfigurationSource)) // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) - .csrf(csrf -> csrf - .ignoringRequestMatchers( - "/api/**", "/login/**", "/oauth2/**", - "/swagger-ui/**", "/v3/api-docs/**", "/orders/**") - .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - ) + // .csrf(csrf -> csrf + // .ignoringRequestMatchers( + // "/api/**", "/login/**", "/oauth2/**", + // "/swagger-ui/**", "/v3/api-docs/**", "/orders/**") + // .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + // ) + .csrf(AbstractHttpConfigurer::disable) // 시큐리티 폼 로그인 비활성화 .formLogin(AbstractHttpConfigurer::disable) // HTTP Basic 인증 비활성화 From d2cf1772920f90502abc05f3ec2f5acfd5c42312 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 28 Aug 2025 00:40:33 +0900 Subject: [PATCH 496/685] =?UTF-8?q?bug:=20=ED=83=80=EC=9E=84=EC=A1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 + .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 2 files changed, 2 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index 1d8d85f7..ae757172 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,3 +14,4 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } + diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 572e3da9da1649d1ba9b590ebefa6f571df6763e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 28 Aug 2025 00:57:45 +0900 Subject: [PATCH 497/685] =?UTF-8?q?bug:=20=ED=83=80=EC=9E=84=EC=A1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.admin.yml | 2 ++ docker-compose.user.yml | 2 ++ nowait-app-admin-api/Dockerfile | 4 +++- nowait-app-user-api/Dockerfile | 4 +++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docker-compose.admin.yml b/docker-compose.admin.yml index 9538a350..fe9a891c 100644 --- a/docker-compose.admin.yml +++ b/docker-compose.admin.yml @@ -23,6 +23,8 @@ services: # - ./nowait-app-admin-api:/app restart: always pull_policy: always + environment: + - TZ=Asia/Seoul volumes: mysql_data: diff --git a/docker-compose.user.yml b/docker-compose.user.yml index baab309f..d7c56560 100644 --- a/docker-compose.user.yml +++ b/docker-compose.user.yml @@ -23,6 +23,8 @@ services: # - ./nowait-app-user-api:/app restart: always pull_policy: always + environment: + - TZ=Asia/Seoul volumes: mysql_data: diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index 457d3fd7..c105544d 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -4,4 +4,6 @@ COPY ${JAR_FILE} nowait-app-admin-api.jar ARG PROFILE=admin -CMD ["java", "-jar", "nowait-app-admin-api.jar", "--spring.profiles.active=admin", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] +ENV TZ=Asia/Seoul + +CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul", "-jar", "nowait-app-admin-api.jar", "--spring.profiles.active=admin"] diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index 0c122c2f..785def27 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -4,4 +4,6 @@ COPY ${JAR_FILE} nowait-app-user-api.jar ARG PROFILE=user -CMD ["java", "-jar", "nowait-app-user-api.jar", "--spring.profiles.active=user", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul"] +ENV TZ=Asia/Seoul + +CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=Asia/Seoul", "-jar", "nowait-app-user-api.jar", "--spring.profiles.active=user"] From 3818835d686a6c0850bb65c2eab36b175cec7e01 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 28 Aug 2025 13:12:36 +0900 Subject: [PATCH 498/685] =?UTF-8?q?refactor:=20StoreDetailReadResponse=20p?= =?UTF-8?q?ublicCode=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/dto/StoreDetailReadResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java index e84c21c1..191eded0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreDetailReadResponse.java @@ -15,6 +15,7 @@ @Builder public class StoreDetailReadResponse { private Long storeId; + private String publicCode; private Long bookmarkId; private Boolean isBookmark; private Long waitingCount; @@ -48,6 +49,7 @@ public static StoreDetailReadResponse fromEntity( return StoreDetailReadResponse.builder() .storeId(store.getStoreId()) + .publicCode(store.getPublicCode()) .bookmarkId(bookmarkId) .isBookmark(isBookmark) .waitingCount(waitingCount) From 2325841150067d0d81a7ddabeaab7d6ff2713aac Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 29 Aug 2025 18:36:39 +0900 Subject: [PATCH 499/685] =?UTF-8?q?refactor:=20path=20publicCode=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/controller/StoreController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index e134bf28..ed034c78 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -46,15 +46,15 @@ public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable, @Aut ); } - @GetMapping("/{storeId}") + @GetMapping("/{publicCode}") @Operation(summary = "주점 ID로 주점 상세 조회", description = "특정 주점을 ID로 조회합니다.") @ApiResponse(responseCode = "200", description = "주점 상세 조회 성공") - public ResponseEntity getStoreById(@PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + public ResponseEntity getStoreById(@PathVariable String publicCode, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - storeService.getStoreByStoreId(storeId, customOAuth2User) + storeService.getStoreByStoreId(publicCode, customOAuth2User) ) ); } From 0cf1418e1d0a939a98fd3e75d654a803eb678427 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 29 Aug 2025 18:36:54 +0900 Subject: [PATCH 500/685] =?UTF-8?q?refactor:=20publicCode=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/dto/StorePageReadResponse.java | 2 ++ .../applicationuser/store/service/StoreService.java | 2 +- .../applicationuser/store/service/StoreServiceImpl.java | 8 +++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java index cabee501..4bc28d85 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StorePageReadResponse.java @@ -15,6 +15,7 @@ @Builder public class StorePageReadResponse { private Long storeId; + private String publicCode; private Long bookmarkId; private Boolean isBookmark; private Long waitingCount; @@ -45,6 +46,7 @@ public static StorePageReadResponse fromEntity(Store store, Long bookmarkId, Lis return StorePageReadResponse.builder() .storeId(store.getStoreId()) + .publicCode(store.getPublicCode()) .bookmarkId(bookmarkId) .isBookmark(isBookmark) .waitingCount(waitingCount) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index 6c0bb7e4..eab832b6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -18,7 +18,7 @@ public interface StoreService { StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable, CustomOAuth2User customOAuth2User); - StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User); + StoreDetailReadResponse getStoreByStoreId(String publicCode, CustomOAuth2User customOAuth2User); List searchByKeywordNative(String name); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index c49b4718..4107506d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -141,15 +141,17 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Override @Transactional(readOnly = true) - public StoreDetailReadResponse getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User) { + public StoreDetailReadResponse getStoreByStoreId(String publicCode, CustomOAuth2User customOAuth2User) { - if (storeId == null) + if (publicCode == null) throw new StoreParamEmptyException(); User user = customOAuth2User.getUser(); - Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); + Long storeId = store.getStoreId();; + String departmentName = departmentRepository.findById(store.getDepartmentId()) .map(Department::getName) .orElse("Unknown Department"); From 488215a1d4da92da109cf724032b39845992838e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 29 Aug 2025 18:44:20 +0900 Subject: [PATCH 501/685] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=EA=B3=BC=20=EC=97=AD=ED=95=A0=20=EC=9D=BC=EC=B9=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/controller/StoreController.java | 2 +- .../com/nowait/applicationuser/store/service/StoreService.java | 2 +- .../nowait/applicationuser/store/service/StoreServiceImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index ed034c78..8eefa2d7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -54,7 +54,7 @@ public ResponseEntity getStoreById(@PathVariable String publicCode, @Authenti .status(HttpStatus.OK) .body( ApiUtils.success( - storeService.getStoreByStoreId(publicCode, customOAuth2User) + storeService.getStoreByPublicCode(publicCode, customOAuth2User) ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java index eab832b6..7f7d8bd6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreService.java @@ -18,7 +18,7 @@ public interface StoreService { StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable pageable, CustomOAuth2User customOAuth2User); - StoreDetailReadResponse getStoreByStoreId(String publicCode, CustomOAuth2User customOAuth2User); + StoreDetailReadResponse getStoreByPublicCode(String publicCode, CustomOAuth2User customOAuth2User); List searchByKeywordNative(String name); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 4107506d..2caf842a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -141,7 +141,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Override @Transactional(readOnly = true) - public StoreDetailReadResponse getStoreByStoreId(String publicCode, CustomOAuth2User customOAuth2User) { + public StoreDetailReadResponse getStoreByPublicCode(String publicCode, CustomOAuth2User customOAuth2User) { if (publicCode == null) throw new StoreParamEmptyException(); From 58ffc50b7e0fd873e8e87a9c85b43fad05e54fc8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 29 Aug 2025 18:45:22 +0900 Subject: [PATCH 502/685] =?UTF-8?q?refactor:=20=EC=9E=85=EB=A0=A5=EA=B0=92?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/store/service/StoreServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 2caf842a..35173642 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -23,6 +23,7 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.applicationuser.store.dto.StoreDepartmentReadResponse; @@ -143,7 +144,7 @@ public StoreDepartmentReadResponse getAllStoresByPageAndDeparments(Pageable page @Transactional(readOnly = true) public StoreDetailReadResponse getStoreByPublicCode(String publicCode, CustomOAuth2User customOAuth2User) { - if (publicCode == null) + if (!StringUtils.hasText(publicCode)) throw new StoreParamEmptyException(); User user = customOAuth2User.getUser(); From 38ab5ce87f10d389e65b26c637b1608ae204f370 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 29 Aug 2025 18:52:08 +0900 Subject: [PATCH 503/685] =?UTF-8?q?refactor:=20publicCode=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/store/dto/StoreSearchResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreSearchResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreSearchResponse.java index 8883ebf3..1a11a085 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreSearchResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreSearchResponse.java @@ -15,6 +15,7 @@ @Builder public class StoreSearchResponse { private Long storeId; + private String publicCode; private Long waitingCount; private Long departmentId; private String departmentName; @@ -34,6 +35,7 @@ public static StoreSearchResponse fromEntity(Store store, List Date: Fri, 29 Aug 2025 18:52:22 +0900 Subject: [PATCH 504/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 82e66e3c..d4ec8ab0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -30,6 +30,7 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; @@ -177,9 +178,15 @@ public WaitingResponseDto myWaitingInfo(Long storeId, CustomOAuth2User customOAu if (storeId == null || userId.trim().isEmpty()) { throw new IllegalArgumentException("Invalid storeId or userId"); } + Long rank = waitingUserRedisRepository.getRank(storeId, userId); Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); String reservationId = waitingUserRedisRepository.getReservationId(storeId, userId); + + if (reservationId == null) { + throw new ReservationNotFoundException(); + } + return WaitingResponseDto.builder() .reservationNumber(reservationId) .rank(rank == null ? -1 : rank.intValue() + 1) From 0599488a2a4527b8314d0b06e9272c898704c5f7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 29 Aug 2025 18:56:20 +0900 Subject: [PATCH 505/685] =?UTF-8?q?refactor:=20publicCode=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/store/dto/StoreWaitingInfo.java | 3 +++ .../nowait/applicationuser/store/service/StoreServiceImpl.java | 1 + 2 files changed, 4 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java index 2cdde824..97a75241 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/dto/StoreWaitingInfo.java @@ -17,6 +17,9 @@ public class StoreWaitingInfo { @Schema(description = "주점 ID", example = "1") private String storeId; + @Schema(description = "주점 publicCode", example = "ad2ndkald24d") + private String publicCode; + @Schema(description = "주점명", example = "노웨이팅 치킨") private String storeName; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java index 35173642..12e352b7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java @@ -282,6 +282,7 @@ public List getStoresByWaitingCount(boolean desc) { imageUrl, department.getName(), storeId, + store.getPublicCode(), storeName, count != null ? count : 0 )); From fea331863736e8646cf83144dd448d3000d53e6d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 2 Sep 2025 17:33:15 +0900 Subject: [PATCH 506/685] =?UTF-8?q?refactor:=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/menu/service/MenuImageService.java | 9 +++++++++ .../menu/repository/MenuImageRepository.java | 2 ++ 2 files changed, 11 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java index e1bdcc5c..b3726fe8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.menu.service; +import java.util.Optional; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -33,6 +35,13 @@ public MenuImageUploadResponse save(Long menuId, MultipartFile file) { Menu menu = menuRepository.findById(menuId) .orElseThrow(MenuNotFoundException::new); + Optional existingMenuImage = menuImageRepository.findByMenuId(menuId); + + existingMenuImage.ifPresent(menuImage -> { + s3Service.delete(menuImage.getFileKey()); + menuImageRepository.delete(menuImage); + }); + S3Service.S3UploadResult uploadResult = s3Service.upload(type, menuId, file).join(); // MenuImage 엔티티 생성 및 저장 diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java index 471464cb..7ad8143a 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java @@ -1,6 +1,7 @@ package com.nowait.domaincorerdb.menu.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -11,4 +12,5 @@ @Repository public interface MenuImageRepository extends JpaRepository { List findByMenu(Menu menu); + Optional findByMenuId(Long menuId); } From f6ca50b0c2bdac6d7e8d1da7c0983eacd98424e7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 18:14:26 +0900 Subject: [PATCH 507/685] =?UTF-8?q?refactor:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EC=83=81=ED=83=9C=EA=B0=92=20CANCELLED=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincorerdb/order/entity/OrderStatus.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java index 322a5218..7e022a5b 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/OrderStatus.java @@ -16,7 +16,10 @@ public enum OrderStatus { COOKING("조리중"), @Schema(description = "조리완료") - COOKED("조리완료"); + COOKED("조리완료"), + + @Schema(description = "주문취소") + CANCELLED("주문취소"); private final String description; } From b462a7ea6f79bdf5c88af80d4561c799722a9d95 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 18:14:42 +0900 Subject: [PATCH 508/685] =?UTF-8?q?refactor:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20deleteOrder?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/domaincorerdb/order/entity/UserOrder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java index 5dbd0295..0da1415a 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java @@ -63,4 +63,8 @@ public void updateStatus(OrderStatus newStatus) { this.status = newStatus; } + public void deleteOrder() { + this.status = OrderStatus.CANCELLED; + } + } From a8040d4aefc89f6376b4bdd73bf1c557fc060b85 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 18:14:49 +0900 Subject: [PATCH 509/685] =?UTF-8?q?refactor:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index dfc88532..eb4a0e9c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -87,6 +87,20 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus return OrderStatusUpdateResponseDto.fromEntity(userOrder); } + @Transactional + public OrderStatusUpdateResponseDto deleteOrder(Long orderId, MemberDetails memberDetails) { + User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); + + if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { + throw new OrderUpdateUnauthorizedException(); + } + + userOrder.deleteOrder(); + + return OrderStatusUpdateResponseDto.fromEntity(userOrder); + } + @Transactional(readOnly = true) public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails, LocalDate date) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); From 6fb3f14e0e9272b0df43c27372a4411dca2132b1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 18:14:56 +0900 Subject: [PATCH 510/685] =?UTF-8?q?refactor:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index 3e7e7efa..f62743b6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -5,6 +5,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -20,6 +21,7 @@ import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -63,4 +65,19 @@ public ResponseEntity updateOrderStatus( .status(HttpStatus.OK) .body(ApiUtils.success(response)); } + + @DeleteMapping("/{orderId}") + @Operation(summary = "주문 삭제", description = "특정 주문을 삭제") + @ApiResponse(responseCode = "200", description = "주문 삭제 성공") + public ResponseEntity deleteOrder( + @PathVariable Long orderId, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + + return ResponseEntity + .status(HttpStatus.OK) + .body(ApiUtils.success( + orderService.deleteOrder(orderId, memberDetails) + )); + } } From 7cfbd09ea67e2317561c4ad969cbcfbc9a6cb458 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:49:00 +0900 Subject: [PATCH 511/685] =?UTF-8?q?chore:=20domain-admin-rdb=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-event/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-event/build.gradle b/nowait-event/build.gradle index 067d51c3..f802a2ff 100644 --- a/nowait-event/build.gradle +++ b/nowait-event/build.gradle @@ -25,6 +25,7 @@ repositories { dependencies { implementation project(':nowait-common') implementation project(':nowait-domain:domain-core-rdb') + implementation project(':nowait-domain:domain-admin-rdb') api 'org.springframework.boot:spring-boot-starter-data-jpa' api 'jakarta.persistence:jakarta.persistence-api:3.1.0' From ce0f3653581f97b99af3e24a06ca87fe22b5b392 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:49:18 +0900 Subject: [PATCH 512/685] =?UTF-8?q?feat:=20CancelOrder=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cancelOrder/entity/CancelOrder.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelOrder.java diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelOrder.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelOrder.java new file mode 100644 index 00000000..7ee01640 --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelOrder.java @@ -0,0 +1,52 @@ +package com.nowait.domainadminrdb.cancelOrder.entity; + +import java.time.Instant; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "cancel_orders") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +public class CancelOrder { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long orderId; + + @Column(name = "store_id", nullable = false) + private Long storeId; + + @Column(nullable = false, length = 256) + private String orderSignature; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 30) + private CancelReason reason; + + @Column(nullable = false) + private Instant cancelAt; + + @PrePersist + void prePersist() { + if (cancelAt == null) cancelAt = Instant.now(); + } +} From b6b45cee06f3cdc418e05a3718bfb60fb6ac028c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:49:28 +0900 Subject: [PATCH 513/685] =?UTF-8?q?feat:=20CancelOrderRepository=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cancelOrder/repository/CancelOrderRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/repository/CancelOrderRepository.java diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/repository/CancelOrderRepository.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/repository/CancelOrderRepository.java new file mode 100644 index 00000000..790a9a3a --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/repository/CancelOrderRepository.java @@ -0,0 +1,10 @@ +package com.nowait.domainadminrdb.cancelOrder.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.nowait.domainadminrdb.cancelOrder.entity.CancelOrder; + +@Repository +public interface CancelOrderRepository extends JpaRepository { +} From c7c9d9ab689c98a326eeb19199565c1b4dcc1fdb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:49:42 +0900 Subject: [PATCH 514/685] =?UTF-8?q?feat:=20CancelOrder=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cancelOrder/dto/CancelOrderRequest.java | 5 ++++ .../cancelOrder/dto/CancelOrderResponse.java | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderRequest.java create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderResponse.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderRequest.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderRequest.java new file mode 100644 index 00000000..76c6e86e --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderRequest.java @@ -0,0 +1,5 @@ +package com.nowait.applicationadmin.cancelOrder.dto; + +import com.nowait.domainadminrdb.cancelOrder.entity.CancelReason; + +public record CancelOrderRequest(CancelReason reason) { } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderResponse.java new file mode 100644 index 00000000..7ee7a2f8 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/dto/CancelOrderResponse.java @@ -0,0 +1,29 @@ +package com.nowait.applicationadmin.cancelOrder.dto; + +import java.time.Instant; + +import com.nowait.domainadminrdb.cancelOrder.entity.CancelOrder; +import com.nowait.domainadminrdb.cancelOrder.entity.CancelReason; + +import lombok.AllArgsConstructor; +import lombok.Builder; + +@Builder +public class CancelOrderResponse { + + private Long id; + private Long orderId; + private String orderSignature; + private CancelReason reason; + private Instant cancelAt; + + public CancelOrder fromEntity() { + return CancelOrder.builder() + .id(id) + .orderId(orderId) + .orderSignature(orderSignature) + .reason(reason) + .cancelAt(cancelAt) + .build(); + } +} From 1b5f744efd08d7412e6d0615f51e08f300964fc2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:49:54 +0900 Subject: [PATCH 515/685] =?UTF-8?q?feat:=20CancelReason=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cancelOrder/entity/CancelReason.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelReason.java diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelReason.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelReason.java new file mode 100644 index 00000000..66245b32 --- /dev/null +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/cancelOrder/entity/CancelReason.java @@ -0,0 +1,21 @@ +package com.nowait.domainadminrdb.cancelOrder.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "주문 상태 Enum") +public enum CancelReason { + + @Schema(description = "단순 취소") + SIMPLE_CANCEL("단순 취소"), + @Schema(description = "메뉴 품절") + SOLD_OUT("메뉴 품절"), + @Schema(description = "기타") + ETC("기타"); + + private final String description; +} + From 5b3e5cc7bd7a69ee3aa0fe804ac7f05faa9d7ce6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:50:07 +0900 Subject: [PATCH 516/685] =?UTF-8?q?feat:=20OrderCancel=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/OrderCancelledEventListener.java | 41 +++++++++++++++++++ .../order/event/OrderCancelledEvent.java | 16 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/listener/OrderCancelledEventListener.java create mode 100644 nowait-event/src/main/java/com/nowait/nowaitevent/order/event/OrderCancelledEvent.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/listener/OrderCancelledEventListener.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/listener/OrderCancelledEventListener.java new file mode 100644 index 00000000..c6fcbd39 --- /dev/null +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/cancelOrder/listener/OrderCancelledEventListener.java @@ -0,0 +1,41 @@ +package com.nowait.applicationadmin.cancelOrder.listener; + +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import com.nowait.domainadminrdb.cancelOrder.entity.CancelOrder; +import com.nowait.domainadminrdb.cancelOrder.repository.CancelOrderRepository; +import com.nowait.nowaitevent.order.event.OrderCancelledEvent; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@RequiredArgsConstructor +@Slf4j +public class OrderCancelledEventListener { + + private final CancelOrderRepository cancelOrderRepository; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void on(OrderCancelledEvent event) { + try { + cancelOrderRepository.save( + CancelOrder.builder() + .orderId(event.getOrderId()) + .storeId(event.getStoreId()) + .orderSignature(event.getOrderSignature()) + .reason(event.getReason()) + .cancelAt(event.getCancelAt()) + .build() + ); + } catch (DataIntegrityViolationException e) { + log.debug("cancel_orders duplicate orderId={}, ignore", event.getOrderId()); + } + } +} diff --git a/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/OrderCancelledEvent.java b/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/OrderCancelledEvent.java new file mode 100644 index 00000000..117655e6 --- /dev/null +++ b/nowait-event/src/main/java/com/nowait/nowaitevent/order/event/OrderCancelledEvent.java @@ -0,0 +1,16 @@ +package com.nowait.nowaitevent.order.event; + +import java.time.Instant; + +import com.nowait.domainadminrdb.cancelOrder.entity.CancelReason; + +import lombok.Value; + +@Value +public class OrderCancelledEvent { + Long orderId; + Long storeId; + String orderSignature; + CancelReason reason; + Instant cancelAt; +} From c29029476b05025116a90ddc5125466073574e6c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:50:28 +0900 Subject: [PATCH 517/685] =?UTF-8?q?refactor:=20RequestBody=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/order/controller/OrderController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java index f62743b6..5a7ea897 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationadmin.cancelOrder.dto.CancelOrderRequest; import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateRequestDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; @@ -21,7 +22,6 @@ import com.nowait.domaincorerdb.user.entity.MemberDetails; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -71,13 +71,14 @@ public ResponseEntity updateOrderStatus( @ApiResponse(responseCode = "200", description = "주문 삭제 성공") public ResponseEntity deleteOrder( @PathVariable Long orderId, + @RequestBody CancelOrderRequest cancelOrderRequest, @AuthenticationPrincipal MemberDetails memberDetails ) { return ResponseEntity .status(HttpStatus.OK) .body(ApiUtils.success( - orderService.deleteOrder(orderId, memberDetails) + orderService.cancelOrder(orderId, cancelOrderRequest, memberDetails) )); } } From 3239b339a054a7fbfdc4515d9cc6209b87f9ba45 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:50:56 +0900 Subject: [PATCH 518/685] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20cancelOrder=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index eb4a0e9c..cfd26222 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -1,5 +1,6 @@ package com.nowait.applicationadmin.order.service; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -10,6 +11,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationadmin.cancelOrder.dto.CancelOrderRequest; import com.nowait.applicationadmin.order.dto.OrderResponseDto; import com.nowait.applicationadmin.order.dto.OrderStatusUpdateResponseDto; import com.nowait.common.enums.Role; @@ -29,6 +31,7 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.nowaitevent.order.event.CookingCompleteEvent; +import com.nowait.nowaitevent.order.event.OrderCancelledEvent; import lombok.RequiredArgsConstructor; @@ -88,7 +91,7 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus } @Transactional - public OrderStatusUpdateResponseDto deleteOrder(Long orderId, MemberDetails memberDetails) { + public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest cancelOrderRequest, MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); @@ -96,7 +99,16 @@ public OrderStatusUpdateResponseDto deleteOrder(Long orderId, MemberDetails memb throw new OrderUpdateUnauthorizedException(); } - userOrder.deleteOrder(); + userOrder.cancelOrder(); + + publisher.publishEvent(new OrderCancelledEvent( + userOrder.getId(), + userOrder.getStore().getStoreId(), + userOrder.getSignature(), + cancelOrderRequest.reason(), + Instant.now() + )); + return OrderStatusUpdateResponseDto.fromEntity(userOrder); } From 57320904c2f6636f89cada28aba51a2d15e7ae98 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 19:51:08 +0900 Subject: [PATCH 519/685] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20cancelOrder=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincorerdb/order/entity/UserOrder.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java index 0da1415a..54fd36a9 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java @@ -30,6 +30,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder public class UserOrder extends BaseTimeEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -49,6 +50,7 @@ public class UserOrder extends BaseTimeEntity { private List orderItems = new ArrayList<>(); private String sessionId; + @Column(length = 10) // 예약자 이름 길이 제한 private String depositorName; @@ -63,7 +65,10 @@ public void updateStatus(OrderStatus newStatus) { this.status = newStatus; } - public void deleteOrder() { + public void cancelOrder() { + if (this.status == OrderStatus.CANCELLED) { + return; + } this.status = OrderStatus.CANCELLED; } From 07f02f11d8214c43aa51dc9df94c57c29bea04da Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 20:08:44 +0900 Subject: [PATCH 520/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiAdminApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java index ae757172..1d8d85f7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java @@ -14,4 +14,3 @@ public static void main(String[] args) { org.springframework.boot.SpringApplication.run(ApiAdminApplication.class, args); } } - From f3482ae63c3c7f704cefe7e5ee869e3c99d0d1eb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 20:20:11 +0900 Subject: [PATCH 521/685] =?UTF-8?q?test:=20=EB=B0=B0=ED=8F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index 2460ef23..a08337be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,4 +13,3 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } - From de254b4dac9e3a137a63f7a18b4cac34f8896f6b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 20:51:59 +0900 Subject: [PATCH 522/685] =?UTF-8?q?bug:=20=EC=A0=84=EB=82=A0=20=EB=A7=A4?= =?UTF-8?q?=EC=B6=9C=20=EC=A7=91=EA=B3=84=20=EA=B3=84=EC=82=B0=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/StatisticCustomRepositoryImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java index bcee06b8..560d0abc 100644 --- a/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java +++ b/nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java @@ -1,7 +1,6 @@ package com.nowait.domainadminrdb.statistic.repository; import static com.nowait.domaincorerdb.order.entity.OrderStatus.*; -import static com.nowait.domaincorerdb.store.entity.QStore.*; import java.time.LocalDate; import java.time.LocalDateTime; @@ -50,6 +49,8 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId, LocalDate date) { LocalDateTime start = date.atStartOfDay(); // 해당 날짜의 자정 LocalDateTime end = date.plusDays(1).atStartOfDay(); + LocalDateTime yesterDayStart = start.minusDays(1); // 어제 00:00 + // 2) target 날짜 해당하는 매출 합산 Integer targetSum = queryFactory .select(u.totalPrice.sum()) @@ -68,8 +69,8 @@ public OrderSalesSumDetail findSalesSumByStoreId(Long storeId, LocalDate date) { .from(u) .where( u.store.storeId.eq(storeId), - u.createdAt.goe(start), - u.createdAt.lt(start.minusDays(1)), + u.createdAt.goe(yesterDayStart), + u.createdAt.lt(start), u.status.eq(COOKED) ) .fetchOne(); From c3bf538397e17ba26a4af55dd2c5dff90db33da9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 3 Sep 2025 20:54:30 +0900 Subject: [PATCH 523/685] =?UTF-8?q?refactor:=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EC=A1=B0=ED=9A=8C=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/dto/PopularMenuDto.java | 1 + .../impl/PopularMenuRedisServiceImpl.java | 18 +++++++++++++++--- .../menu/repository/MenuImageRepository.java | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java index 828be84a..ebf17cfc 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/PopularMenuDto.java @@ -9,4 +9,5 @@ public class PopularMenuDto { private Long menuId; private String menuName; private Long soldCount; + private String imageUrl; } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java index 01119cbb..e5f43a16 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/PopularMenuRedisServiceImpl.java @@ -13,6 +13,8 @@ import com.nowait.common.enums.Role; import com.nowait.domainadminrdb.statistic.exception.StatisticViewUnauthorizedException; import com.nowait.domaincorerdb.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.entity.MenuImage; +import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; @@ -29,6 +31,7 @@ public class PopularMenuRedisServiceImpl implements PopularMenuRedisService { private final MenuCounterService menuCounterService; private final MenuRepository menuRepository; private final UserRepository userRepository; + private final MenuImageRepository menuImageRepository; @Override public List getTodayTop5(MemberDetails memberDetails) { @@ -36,8 +39,8 @@ public List getTodayTop5(MemberDetails memberDetails) { User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StatisticViewUnauthorizedException(); + if (!Role.SUPER_ADMIN.equals(user.getRole())) { + if (storeId == null) throw new StatisticViewUnauthorizedException(); } Set> tuples = menuCounterService.getTopMenus(storeId, 5); @@ -53,13 +56,22 @@ public List getTodayTop5(MemberDetails memberDetails) { Menu::getName )); + var firstImageByMenu = menuImageRepository.findByMenuIdInOrderByIdAsc(menuIds).stream() + .collect(Collectors.toMap( + menuImage -> menuImage.getMenu().getId(), + MenuImage::getImageUrl, + (existing, replacement) -> existing + )); + return tuples.stream() .map(tuple -> { Long menuId = Long.parseLong(tuple.getValue()); String menuName = menuIdToNameMap.getOrDefault(menuId, "Unknown Menu"); + String imageUrl = firstImageByMenu.get(menuId); + long sold = tuple.getScore().longValue(); - return new PopularMenuDto(menuId, menuName, tuple.getScore().longValue()); + return new PopularMenuDto(menuId, menuName, sold, imageUrl); }) .toList(); } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java index 7ad8143a..94a6649c 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuImageRepository.java @@ -13,4 +13,5 @@ public interface MenuImageRepository extends JpaRepository { List findByMenu(Menu menu); Optional findByMenuId(Long menuId); + List findByMenuIdInOrderByIdAsc(List menuIds); } From 8fbef441b0e6ec5c34b94030d5521f027639f278 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:32:26 +0900 Subject: [PATCH 524/685] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=84=9C=EB=B2=84=20cors=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java index 4c51b670..da611607 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173","http://localhost:63342", "https://no-wait-fe-nowait-admin-2y5y.vercel.app", "https://nowait-admin.co.kr")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173","http://localhost:63342", "https://no-wait-fe-nowait-admin-2y5y.vercel.app", "https://nowait-admin.co.kr", "https://www.nowait-admin.com")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From 7f85c2ff1f201a3bd60f13104dc03c96d01c0678 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:33:22 +0900 Subject: [PATCH 525/685] =?UTF-8?q?refactor:=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/oauth2/CustomOAuth2UserService.java | 6 +++ .../domaincorerdb/user/entity/User.java | 45 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java index a7e12685..048dc43b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java @@ -1,5 +1,6 @@ package com.nowait.applicationuser.oauth.oauth2; +import java.time.LocalDateTime; import java.util.Optional; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -50,11 +51,16 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic User user = User.builder() .email(oAuth2Response.getEmail()) + .phoneNumber("") .nickname(oAuth2Response.getNickName()) .profileImage(oAuth2Response.getProfileImage()) .socialType(SocialType.KAKAO) .role(Role.USER) // 일반 유저 설정 .storeId(0L) + .phoneEntered(false) + .isMarketingAgree(false) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) .build(); userRepository.save(user); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java index 86d45d9b..587f5aa8 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java @@ -1,9 +1,15 @@ package com.nowait.domaincorerdb.user.entity; +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.springframework.cglib.core.Local; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.security.crypto.password.PasswordEncoder; import com.nowait.common.enums.Role; import com.nowait.common.enums.SocialType; +import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -17,18 +23,23 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "users") @Getter -public class User { +@SuperBuilder +public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 생성 private Long id; @Column(nullable = false, unique = true) private String email; // 카카오 이메일 + @Column(nullable = true) + private String phoneNumber; // 사용자 전화번호 + @Column(nullable = false) private String password; // 관리자 패스워드 @@ -44,11 +55,22 @@ public class User { @Enumerated(EnumType.STRING) private Role role; + @Column(nullable = false) + private Boolean isMarketingAgree = false; + + @Column(nullable = false) + private Boolean phoneEntered = false; + private Long storeId; - @Builder - public User(String email,String password, String nickname, String profileImage, SocialType socialType, - Role role, Long storeId) { + @LastModifiedDate + @Column(nullable = false) + private LocalDateTime updatedAt; + + + public User(LocalDateTime createdAt, String email,String password, String nickname, String profileImage, SocialType socialType, + Role role, Long storeId, LocalDateTime updatedAt ) { + super(createdAt); this.email = email; this.password = password; this.nickname = nickname; @@ -56,6 +78,7 @@ public User(String email,String password, String nickname, String profileImage, this.socialType = socialType; this.role = role; this.storeId = storeId; + this.updatedAt = updatedAt; } public static User createUserWithId(Long userId, String email, String nickname, String profileImage, @@ -67,6 +90,8 @@ public static User createUserWithId(Long userId, String email, String nickname, .socialType(socialType) .role(role) .storeId(storeId) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) .build(); user.id = userId; @@ -77,7 +102,19 @@ public static User createUserWithId(Long userId, String email, String nickname, public void updateNickname(String nickname){ this.nickname = nickname; } + public void encodePassword(PasswordEncoder passwordEncoder) { password = passwordEncoder.encode(password); } + + public void setPhoneNumberAndMarkEntered(String phoneNumber, LocalDateTime ts) { + this.phoneNumber = phoneNumber; + this.phoneEntered = true; + this.updatedAt = ts; + } + + public void setIsMarketingAgree(boolean agree, LocalDateTime ts) { + this.isMarketingAgree = agree; + this.updatedAt = ts; + } } From 8296a5c50c6e97cd484c274c85cf1ff083075186 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:33:51 +0900 Subject: [PATCH 526/685] =?UTF-8?q?refactor:=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/security/jwt/JwtUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtUtil.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtUtil.java index 763c320a..3df48aab 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtUtil.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtUtil.java @@ -23,11 +23,14 @@ public JwtUtil(@Value("${jwt.secret}") String secret) { ); } - public String createAccessToken(String tokenCategory, Long userId, String role, Long expiredMs) { + public String createAccessToken(String tokenCategory, Long userId, String role, boolean phoneEntered, + boolean marketingAgree, Long expiredMs) { return Jwts.builder() .claim("tokenCategory", tokenCategory) // accessToken .claim("userId", userId) .claim("role", role) + .claim("phoneEntered", phoneEntered) + .claim("marketingAgree", marketingAgree) .issuedAt(new Date(System.currentTimeMillis())) .expiration(new Date(System.currentTimeMillis() + expiredMs)) .signWith(secretKey) From 968ae1be740fe4d942cfe640ba2f743e914d34ed Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:34:47 +0900 Subject: [PATCH 527/685] =?UTF-8?q?refactor:=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationadmin/user/dto/ManagerSignupRequestDto.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java index 7f807bea..0dd4fbcd 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java @@ -1,5 +1,7 @@ package com.nowait.applicationadmin.user.dto; +import java.time.LocalDateTime; + import com.nowait.common.enums.Role; import com.nowait.common.enums.SocialType; import com.nowait.domaincorerdb.user.entity.User; @@ -7,7 +9,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.NoArgsConstructor; @@ -39,10 +40,13 @@ public class ManagerSignupRequestDto { public User toEntity() { return User.builder() .email(email) + .phoneNumber("") .password(password) .nickname(nickname) .socialType(SocialType.LOCAL) .role(Role.MANAGER) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) .build(); } From 5b2faf5488b3bbd4ed7a8b4c56e12a8e5841308d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:34:52 +0900 Subject: [PATCH 528/685] =?UTF-8?q?refactor:=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/oauth2/OAuth2LoginSuccessHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 4a843688..c6e2dcf7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -38,13 +38,14 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal(); + CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); User user = customUserDetails.getUser(); Long userId = customUserDetails.getUserId(); String role = authentication.getAuthorities().iterator().next().getAuthority(); // JWT 발급 - String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, 30 * 60 * 1000L); // 30분 + String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, + Boolean.TRUE.equals(user.getPhoneEntered()), Boolean.TRUE.equals(user.getIsMarketingAgree()),60 * 60 * 1000L); // 1시간 String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, 30L * 24 * 60 * 60 * 1000L); // 30일 // 1. refreshToken을 DB에 저장 or update From 008972a680770042d138ffc83802cb12e46b3bfb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:35:01 +0900 Subject: [PATCH 529/685] =?UTF-8?q?refactor:=20=EA=B8=80=EC=9E=90=EC=88=98?= =?UTF-8?q?=20=EC=A0=9C=ED=95=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/domaincorerdb/store/entity/Store.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java index a635dab2..109c9c45 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/Store.java @@ -40,13 +40,13 @@ public class Store extends BaseTimeEntity { @Column(nullable = true, length = 200) private String location; - @Column(nullable = true, length = 200) + @Column(nullable = true, length = 250) private String description; @Column(nullable = true, length = 200) private String noticeTitle; - @Column(nullable = true, length = 200) + @Column(nullable = true, length = 500) private String noticeContent; @Column(nullable = true, length = 200) From fa01dfb9aa3c9e563ddfcbae8ab3d3d4d3f9115a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:35:15 +0900 Subject: [PATCH 530/685] =?UTF-8?q?refactor:=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../token/controller/TokenController.java | 82 +++++++++++-------- .../domaincorerdb/user/entity/User.java | 3 - 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index fc6a99e2..5c874013 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -11,6 +11,9 @@ import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; import com.nowait.applicationuser.token.service.TokenService; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -22,37 +25,50 @@ @RequestMapping("/api/refresh-token") @Slf4j public class TokenController { - private final JwtUtil jwtUtil; - private final TokenService tokenService; - @Value("${jwt.access-token-expiration-ms}") - private long accessTokenExpiration; - @Value("${jwt.refresh-token-expiration-ms}") - private long refreshTokenExpiration; - - @PostMapping - @Operation(summary = "리프레시 토큰", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰과 리프레시 토큰을 발급합니다.") - @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰과 리프레시 토큰 발급 성공") - public ResponseEntity refreshToken( - @CookieValue(value = "refreshToken", required = false) String refreshToken) { - - if (refreshToken == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh token not found in cookies"); - } - - // 리프레시 토큰 검증 - Long userId = jwtUtil.getUserId(refreshToken); - String role = jwtUtil.getRole(refreshToken); - - if (tokenService.validateToken(refreshToken, userId)){ - String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, accessTokenExpiration); - String newRefreshToken = jwtUtil.createRefreshToken("refreshToken", userId, refreshTokenExpiration); - - tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); - - AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); - return ResponseEntity.ok().body(authenticationResponse); - } - - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); - } + private final JwtUtil jwtUtil; + private final TokenService tokenService; + private final UserRepository userRepository; + @Value("${jwt.access-token-expiration-ms}") + private long accessTokenExpiration; + @Value("${jwt.refresh-token-expiration-ms}") + private long refreshTokenExpiration; + + @PostMapping + @Operation(summary = "리프레시 토큰", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰과 리프레시 토큰을 발급합니다.") + @ApiResponse(responseCode = "200", description = "새로운 액세스 토큰과 리프레시 토큰 발급 성공") + public ResponseEntity refreshToken( + @CookieValue(value = "refreshToken", required = false) String refreshToken) { + + if (refreshToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh token not found in cookies"); + } + + // 리프레시 토큰 검증 + Long userId = jwtUtil.getUserId(refreshToken); + String role = jwtUtil.getRole(refreshToken); + User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); + + if (tokenService.validateToken(refreshToken, userId)) { + String newAccessToken = jwtUtil.createAccessToken( + "accessToken", + userId, + role, + Boolean.TRUE.equals(user.getPhoneEntered()), + Boolean.TRUE.equals(user.getIsMarketingAgree()), + accessTokenExpiration + ); + String newRefreshToken = jwtUtil.createRefreshToken( + "refreshToken", + userId, + refreshTokenExpiration + ); + + tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); + + AuthenticationResponse authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); + return ResponseEntity.ok().body(authenticationResponse); + } + + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); + } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java index 587f5aa8..bac239a2 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java @@ -1,9 +1,7 @@ package com.nowait.domaincorerdb.user.entity; -import java.time.LocalDate; import java.time.LocalDateTime; -import org.springframework.cglib.core.Local; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.security.crypto.password.PasswordEncoder; @@ -20,7 +18,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; From 85aef8aba9fe6970797e89586f7e1ab2c0fb856f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:35:26 +0900 Subject: [PATCH 531/685] =?UTF-8?q?refactor:=20existsByPhoneNumber=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincorerdb/user/repository/UserRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java index b22b9fc9..48a01b47 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java @@ -11,4 +11,5 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByNickname(String nickName); + boolean existsByPhoneNumber(String phoneNumber); } From 736c27a7f299675e1d31874e490cfc3e7675616c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:35:51 +0900 Subject: [PATCH 532/685] =?UTF-8?q?feat:=20=ED=9C=B4=EB=8C=80=ED=8F=B0=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 42 +++++++++++++++++++ .../user/service/UserService.java | 42 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java new file mode 100644 index 00000000..eddbadab --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -0,0 +1,42 @@ +package com.nowait.applicationuser.user.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationuser.user.dto.UserUpdateRequest; +import com.nowait.applicationuser.user.service.UserService; +import com.nowait.common.api.ApiUtils; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/users") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @PutMapping("/optional-info") + public ResponseEntity putOptional( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @Valid @RequestBody UserUpdateRequest req) { + + String newAccessToken = userService.putOptional(customOAuth2User.getUserId(), req.phoneNumber(), + req.consent()); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + newAccessToken + ) + ); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java new file mode 100644 index 00000000..f9bc27cc --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -0,0 +1,42 @@ +package com.nowait.applicationuser.user.service; + +import java.time.LocalDateTime; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.security.jwt.JwtUtil; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + private final JwtUtil jwtUtil; + + @Transactional + public String putOptional(Long userId, String phoneNumber, boolean consent) { + + User user = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + + if (userRepository.existsByPhoneNumber(phoneNumber)) { + throw new IllegalArgumentException("Phone number already in use"); + } + + user.setPhoneNumberAndMarkEntered(phoneNumber, LocalDateTime.now()); + user.setIsMarketingAgree(consent, LocalDateTime.now()); + + String role = "ROLE_" + user.getRole().name(); + + return jwtUtil.createAccessToken("accessToken", user.getId(), role, + Boolean.TRUE.equals(user.getPhoneEntered()), + Boolean.TRUE.equals(user.getIsMarketingAgree()), + 60 * 60 * 1000L); + } +} From 1f2032e9309647bc9ab3d0819185127c8cd723dd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 14:35:57 +0900 Subject: [PATCH 533/685] =?UTF-8?q?feat:=20=ED=9C=B4=EB=8C=80=ED=8F=B0=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=A7=88=EC=BC=80=ED=8C=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20DTO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/user/dto/UserUpdateRequest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java new file mode 100644 index 00000000..68dc73b5 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java @@ -0,0 +1,10 @@ +package com.nowait.applicationuser.user.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public record UserUpdateRequest( + @NotBlank + @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "휴대폰 번호는 010-0000-0000 형식이어야 합니다.") + String phoneNumber, + Boolean consent) { } From 3a6fb085f3aed3d8e271e7bdcd11dd6905e2b1a7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 15:01:31 +0900 Subject: [PATCH 534/685] =?UTF-8?q?refactor:=20=EC=9C=A0=ED=9A=A8=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=ED=8C=8C=EC=8B=B1=20=EC=8B=9C?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EB=AF=B8=EC=B2=98=EB=A6=AC=20=E2=86=92?= =?UTF-8?q?=20500=20=EA=B0=80=EB=8A=A5=EC=84=B1=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../token/controller/TokenController.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index 5c874013..53368747 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -44,8 +44,15 @@ public ResponseEntity refreshToken( } // 리프레시 토큰 검증 - Long userId = jwtUtil.getUserId(refreshToken); - String role = jwtUtil.getRole(refreshToken); + Long userId; + String role; + try { + userId = jwtUtil.getUserId(refreshToken); + role = jwtUtil.getRole(refreshToken); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token"); + } + User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); if (tokenService.validateToken(refreshToken, userId)) { From 919c851574cd17637f6d723f55a30bd8de8ac039 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 15:02:48 +0900 Subject: [PATCH 535/685] =?UTF-8?q?refactor:=20Null=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=20Boolean=20=EC=96=B8=EB=B0=95=EC=8B=B1=EC=9C=BC=EB=A1=9C=20NP?= =?UTF-8?q?E=20=EC=9C=84=ED=97=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/user/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index eddbadab..fcb05808 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -29,7 +29,7 @@ public ResponseEntity putOptional( @Valid @RequestBody UserUpdateRequest req) { String newAccessToken = userService.putOptional(customOAuth2User.getUserId(), req.phoneNumber(), - req.consent()); + Boolean.TRUE.equals(req.consent())); return ResponseEntity .status(HttpStatus.OK) From faabc3146061878370b48ef01f28945ee3da79d9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 15:03:59 +0900 Subject: [PATCH 536/685] =?UTF-8?q?refactor:=20NPE=20=EB=B0=A9=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20primitive=20boolean=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/user/dto/UserUpdateRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java index 68dc73b5..f9993f19 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java @@ -7,4 +7,4 @@ public record UserUpdateRequest( @NotBlank @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "휴대폰 번호는 010-0000-0000 형식이어야 합니다.") String phoneNumber, - Boolean consent) { } + boolean consent) { } From 279bba7e82b78ebb9d8bb612c9909f535f103102 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 15:05:58 +0900 Subject: [PATCH 537/685] =?UTF-8?q?refactor:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=82=AC:=20?= =?UTF-8?q?=EC=9E=90=EA=B8=B0=20=EC=9E=90=EC=8B=A0=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=8B=9C=20=EC=98=A4=ED=83=90=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/user/service/UserService.java | 4 ++-- .../nowait/domaincorerdb/user/repository/UserRepository.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java index f9bc27cc..bdd65d56 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -25,8 +25,8 @@ public String putOptional(Long userId, String phoneNumber, boolean consent) { User user = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - if (userRepository.existsByPhoneNumber(phoneNumber)) { - throw new IllegalArgumentException("Phone number already in use"); + if (userRepository.existsByPhoneNumberAndIdNot(phoneNumber, userId)) { + throw new IllegalArgumentException("이미 사용 중인 휴대폰 번호입니다."); } user.setPhoneNumberAndMarkEntered(phoneNumber, LocalDateTime.now()); diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java index 48a01b47..5653ee4b 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java @@ -11,5 +11,5 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByNickname(String nickName); - boolean existsByPhoneNumber(String phoneNumber); + boolean existsByPhoneNumberAndIdNot(String phoneNumber, Long id); } From c1b863e47073827a6490107bc7b23ba885727170 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 18:37:02 +0900 Subject: [PATCH 538/685] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 12 +++++-- .../user/service/UserService.java | 36 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index fcb05808..e60b86ed 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -3,11 +3,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; import com.nowait.applicationuser.user.dto.UserUpdateRequest; import com.nowait.applicationuser.user.service.UserService; import com.nowait.common.api.ApiUtils; @@ -25,17 +27,21 @@ public class UserController { @PutMapping("/optional-info") public ResponseEntity putOptional( - @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @CookieValue(value = "accessToken", required = false) String accessToken, @Valid @RequestBody UserUpdateRequest req) { - String newAccessToken = userService.putOptional(customOAuth2User.getUserId(), req.phoneNumber(), + if (accessToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("accessToken not found in cookies"); + } + + AuthenticationResponse authenticationResponse = userService.putOptional(accessToken, req.phoneNumber(), Boolean.TRUE.equals(req.consent())); return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - newAccessToken + authenticationResponse ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java index bdd65d56..a8e5b890 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -2,10 +2,15 @@ import java.time.LocalDateTime; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.security.jwt.JwtUtil; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.service.TokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; @@ -17,13 +22,17 @@ public class UserService { private final UserRepository userRepository; + private final TokenService tokenService; private final JwtUtil jwtUtil; @Transactional - public String putOptional(Long userId, String phoneNumber, boolean consent) { + public AuthenticationResponse putOptional(String accessToken, String phoneNumber, boolean consent) { - User user = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); + Long userId = jwtUtil.getUserId(accessToken);; + String role = jwtUtil.getRole(accessToken); + AuthenticationResponse authenticationResponse; + + User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); if (userRepository.existsByPhoneNumberAndIdNot(phoneNumber, userId)) { throw new IllegalArgumentException("이미 사용 중인 휴대폰 번호입니다."); @@ -32,11 +41,24 @@ public String putOptional(Long userId, String phoneNumber, boolean consent) { user.setPhoneNumberAndMarkEntered(phoneNumber, LocalDateTime.now()); user.setIsMarketingAgree(consent, LocalDateTime.now()); - String role = "ROLE_" + user.getRole().name(); - - return jwtUtil.createAccessToken("accessToken", user.getId(), role, + String newAccessToken = jwtUtil.createAccessToken( + "accessToken", + userId, + role, Boolean.TRUE.equals(user.getPhoneEntered()), Boolean.TRUE.equals(user.getIsMarketingAgree()), - 60 * 60 * 1000L); + 60 * 60 * 1000L + ); + String newRefreshToken = jwtUtil.createRefreshToken( + "refreshToken", + userId, + 60 * 60 * 1000L + ); + + tokenService.updateRefreshToken(userId, accessToken, newRefreshToken); + + authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); + + return authenticationResponse; } } From f4feeaf4ac99464afe0d4d9fd1e06294fbea0341 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 18:39:11 +0900 Subject: [PATCH 539/685] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/user/controller/UserController.java | 6 +++--- .../nowait/applicationuser/user/service/UserService.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index e60b86ed..dadb571e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -27,14 +27,14 @@ public class UserController { @PutMapping("/optional-info") public ResponseEntity putOptional( - @CookieValue(value = "accessToken", required = false) String accessToken, + @CookieValue(value = "refreshToken", required = false) String refreshToken, @Valid @RequestBody UserUpdateRequest req) { - if (accessToken == null) { + if (refreshToken == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("accessToken not found in cookies"); } - AuthenticationResponse authenticationResponse = userService.putOptional(accessToken, req.phoneNumber(), + AuthenticationResponse authenticationResponse = userService.putOptional(refreshToken, req.phoneNumber(), Boolean.TRUE.equals(req.consent())); return ResponseEntity diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java index a8e5b890..9b202e09 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -26,10 +26,10 @@ public class UserService { private final JwtUtil jwtUtil; @Transactional - public AuthenticationResponse putOptional(String accessToken, String phoneNumber, boolean consent) { + public AuthenticationResponse putOptional(String refreshToken, String phoneNumber, boolean consent) { - Long userId = jwtUtil.getUserId(accessToken);; - String role = jwtUtil.getRole(accessToken); + Long userId = jwtUtil.getUserId(refreshToken);; + String role = jwtUtil.getRole(refreshToken); AuthenticationResponse authenticationResponse; User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); @@ -55,7 +55,7 @@ public AuthenticationResponse putOptional(String accessToken, String phoneNumber 60 * 60 * 1000L ); - tokenService.updateRefreshToken(userId, accessToken, newRefreshToken); + tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); From ed039b600fc79f25171cc54e9642199cd270c138 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 18:52:37 +0900 Subject: [PATCH 540/685] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../token/dto/NewAccessTokenResponse.java | 15 +++++++++++++ .../user/controller/UserController.java | 14 ++++--------- .../user/dto/UserUpdateRequest.java | 3 ++- .../user/service/UserService.java | 21 ++++++------------- 4 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java new file mode 100644 index 00000000..f42e7979 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java @@ -0,0 +1,15 @@ +package com.nowait.applicationuser.token.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@RequiredArgsConstructor +@Getter +@ToString(exclude = {"accessToken"}) // 로깅 시 토큰 노출 방지 +public class NewAccessTokenResponse { + @JsonProperty("access_token") + private final String accessToken; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index dadb571e..b0179d21 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -2,7 +2,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -10,10 +9,10 @@ import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.dto.NewAccessTokenResponse; import com.nowait.applicationuser.user.dto.UserUpdateRequest; import com.nowait.applicationuser.user.service.UserService; import com.nowait.common.api.ApiUtils; -import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -27,21 +26,16 @@ public class UserController { @PutMapping("/optional-info") public ResponseEntity putOptional( - @CookieValue(value = "refreshToken", required = false) String refreshToken, @Valid @RequestBody UserUpdateRequest req) { - if (refreshToken == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("accessToken not found in cookies"); - } - - AuthenticationResponse authenticationResponse = userService.putOptional(refreshToken, req.phoneNumber(), - Boolean.TRUE.equals(req.consent())); + NewAccessTokenResponse newAccessTokenResponse = userService.putOptional(req.phoneNumber(), + Boolean.TRUE.equals(req.consent()), req.accessToken()); return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - authenticationResponse + newAccessTokenResponse ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java index f9993f19..e5dd48fa 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java @@ -7,4 +7,5 @@ public record UserUpdateRequest( @NotBlank @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "휴대폰 번호는 010-0000-0000 형식이어야 합니다.") String phoneNumber, - boolean consent) { } + boolean consent, + String accessToken) { } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java index 9b202e09..38496b9f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -2,14 +2,12 @@ import java.time.LocalDateTime; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.dto.NewAccessTokenResponse; import com.nowait.applicationuser.token.service.TokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -26,10 +24,10 @@ public class UserService { private final JwtUtil jwtUtil; @Transactional - public AuthenticationResponse putOptional(String refreshToken, String phoneNumber, boolean consent) { + public NewAccessTokenResponse putOptional(String phoneNumber, boolean consent, String accessToken) { - Long userId = jwtUtil.getUserId(refreshToken);; - String role = jwtUtil.getRole(refreshToken); + Long userId = jwtUtil.getUserId(accessToken);; + String role = jwtUtil.getRole(accessToken); AuthenticationResponse authenticationResponse; User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); @@ -49,16 +47,9 @@ public AuthenticationResponse putOptional(String refreshToken, String phoneNumbe Boolean.TRUE.equals(user.getIsMarketingAgree()), 60 * 60 * 1000L ); - String newRefreshToken = jwtUtil.createRefreshToken( - "refreshToken", - userId, - 60 * 60 * 1000L - ); - - tokenService.updateRefreshToken(userId, refreshToken, newRefreshToken); - authenticationResponse = new AuthenticationResponse(newAccessToken, newRefreshToken); + NewAccessTokenResponse newAccessTokenResponse = new NewAccessTokenResponse(newAccessToken); - return authenticationResponse; + return newAccessTokenResponse; } } From 4b5f020155c6caf8fe64e1ccba5560d7ce447712 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 5 Sep 2025 21:11:52 +0900 Subject: [PATCH 541/685] =?UTF-8?q?refactor:=20=EB=8C=80=EA=B8=B0=EC=97=B4?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=ED=9C=B4=EB=8C=80=ED=8F=B0=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingUserResponse.java | 10 +++++-- .../service/ReservationService.java | 29 ++++++++++++------- .../user/repository/UserRepository.java | 3 ++ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java index 2dbbc286..3f8838fe 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java @@ -18,6 +18,9 @@ public class WaitingUserResponse { @Schema(description = "예약 ID", example = "16-20240201-0002") private String reservationNumber; + @Schema(description = "휴대폰 번호", example = "010-1234-5678") + private String phoneNumber; + @Schema(description = "유저 ID", example = "16") private String userId; @@ -47,6 +50,7 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { .reservationNumber(reservation.getReservationNumber()) .userId(reservation.getUser().getId().toString()) .partySize(reservation.getPartySize()) + .phoneNumber(reservation.getUser().getPhoneNumber()) .userName(reservation.getUser().getNickname()) .createdAt(reservation.getRequestedAt()) .updatedAt(reservation.getUpdatedAt()) @@ -54,11 +58,13 @@ public static WaitingUserResponse fromEntity(Reservation reservation) { .build(); } - public static WaitingUserResponse fromRedis(String reservationId, String userId, Integer partySize, String userName, - LocalDateTime createdAt, LocalDateTime calledAt,String status, Double score) { + public static WaitingUserResponse fromRedis(String reservationId, String userId, String phoneNumber, + Integer partySize, String userName, + LocalDateTime createdAt, LocalDateTime calledAt, String status, Double score) { return WaitingUserResponse.builder() .reservationNumber(reservationId) .userId(userId) + .phoneNumber(phoneNumber) .partySize(partySize) .userName(userName) .createdAt(createdAt) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 63139bbf..fb461266 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -6,11 +6,11 @@ import java.time.LocalTime; import java.time.ZoneId; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; @@ -125,11 +125,17 @@ public List getAllWaitingUserDetails(Long storeId) { List userIdLongs = userIds.stream() .map(Long::valueOf) .toList(); - Map nicknameMap = userRepository.findAllById(userIdLongs).stream() - .collect(Collectors.toMap( - u -> u.getId().toString(), - User::getNickname - )); + + List userList = userRepository.findByIdIn(userIdLongs); + + Map nicknameMap = new HashMap<>(userList.size()); + Map phoneNumberMap = new HashMap<>(userList.size()); + + for (User user : userList) { + String key = user.getId().toString(); + nicknameMap.put(key, user.getNickname()); + phoneNumberMap.put(key, user.getPhoneNumber()); + } // 4) Redis 파이프라인: partySize, status, reservationId String pk = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; @@ -174,9 +180,11 @@ public List getAllWaitingUserDetails(Long storeId) { : null; String userName = nicknameMap.getOrDefault(userId, "Unknown"); + String phoneNumber = phoneNumberMap.getOrDefault(userId, "Unknown"); result.add( - WaitingUserResponse.fromRedis(reservationId, userId, partySize, userName, createdAt, calledAt, status, + WaitingUserResponse.fromRedis(reservationId, userId, phoneNumber, partySize, userName, createdAt, + calledAt, status, score)); } @@ -269,8 +277,10 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me } else { if (reservationNumber != null) { try { - waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); - } catch (Exception ignore) {} + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), + reservationNumber); + } catch (Exception ignore) { + } } // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 @@ -323,7 +333,6 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me } } - /** * 공통 메서드 */ diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java index 5653ee4b..090025c3 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/repository/UserRepository.java @@ -1,5 +1,7 @@ package com.nowait.domaincorerdb.user.repository; +import java.util.Collection; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,5 +13,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByNickname(String nickName); + List findByIdIn(Collection ids); boolean existsByPhoneNumberAndIdNot(String phoneNumber, Long id); } From 6ac2c8c37d8dbe308d617905aa4969850deb91be Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 9 Sep 2025 21:51:03 +0900 Subject: [PATCH 542/685] =?UTF-8?q?refactor:=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/user/dto/UserUpdateRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java index e5dd48fa..37d385d5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/dto/UserUpdateRequest.java @@ -5,7 +5,7 @@ public record UserUpdateRequest( @NotBlank - @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "휴대폰 번호는 010-0000-0000 형식이어야 합니다.") + @Pattern(regexp = "^010\\d{4}\\d{4}$", message = "휴대폰 번호는 01000000000 형식이어야 합니다.") String phoneNumber, boolean consent, String accessToken) { } From f0c4cd3eda1b7c2fd11e784c2e79b1c117dd0ebc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 12 Sep 2025 15:22:45 +0900 Subject: [PATCH 543/685] =?UTF-8?q?refactor:=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EC=A0=95=EC=B1=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/nowait/ApiUserApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java index a08337be..2460ef23 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java +++ b/nowait-app-user-api/src/main/java/com/nowait/ApiUserApplication.java @@ -13,3 +13,4 @@ public static void main(String[] args) { SpringApplication.run(ApiUserApplication.class, args); } } + From 33e1838ba779a6845fab134ed97c34f9751dc4ca Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 12 Sep 2025 16:43:13 +0900 Subject: [PATCH 544/685] =?UTF-8?q?refactor:=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/order/service/OrderService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 5b317dcc..df3626b0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -17,16 +17,19 @@ import com.nowait.applicationuser.order.dto.OrderMenuDto; import com.nowait.applicationuser.order.dto.OrderResponseDto; import com.nowait.domaincorerdb.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.order.entity.OrderItem; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; +import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.order.repository.OrderItemRepository; import com.nowait.domaincorerdb.order.repository.OrderRepository; import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import lombok.RequiredArgsConstructor; @@ -49,7 +52,7 @@ public OrderCreateResponseDto createOrder(String publicCode, Long tableId, // 1. Store 조회 Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) - .orElseThrow(() -> new IllegalArgumentException("store not found")); + .orElseThrow(StoreNotFoundException::new); // 2. UserOrder 생성 및 signature 저장 UserOrder order = UserOrder.builder() @@ -77,7 +80,7 @@ public OrderCreateResponseDto createOrder(String publicCode, Long tableId, List orderItems = orderCreateRequestDto.getItems().stream() .map(item -> { Menu menu = Optional.ofNullable(menuMap.get(item.getMenuId())) - .orElseThrow(() -> new IllegalArgumentException("menu not found: " + item.getMenuId())); + .orElseThrow(MenuNotFoundException::new); return OrderItem.builder() .userOrder(savedOrder) .menu(menu) @@ -127,7 +130,7 @@ private static void parameterValidation(String publicCode, Long tableId, OrderCr throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getDepositorName().length() > 20) { - throw new IllegalArgumentException("Depositor name is too long"); + throw new DepositorNameTooLongException(); } } private String generateOrderSignature(String storeId, Long tableId, List items) { From 2abd8d30a2bb6eaa234b22d26c715a5c143c5b52 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 12 Sep 2025 16:45:13 +0900 Subject: [PATCH 545/685] =?UTF-8?q?refactor:=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index df3626b0..4f7f5b9e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -41,6 +41,7 @@ public class OrderService { private final StoreRepository storeRepository; private final MenuRepository menuRepository; private final OrderItemRepository orderItemRepository; + @Transactional public OrderCreateResponseDto createOrder(String publicCode, Long tableId, OrderCreateRequestDto orderCreateRequestDto, String sessionId) { @@ -89,18 +90,18 @@ public OrderCreateResponseDto createOrder(String publicCode, Long tableId, }) .collect(Collectors.toList()); - orderItemRepository.saveAll(orderItems); // 5. 응답 반환 - return OrderCreateResponseDto.fromEntity(savedOrder,orderItems); + return OrderCreateResponseDto.fromEntity(savedOrder, orderItems); } @Transactional(readOnly = true) public List getOrderItemsGroupByOrderId( String publicCode, Long tableId, String sessionId) { - List userOrders = orderRepository.findByStore_PublicCodeAndTableIdAndSessionId(publicCode, tableId, sessionId); + List userOrders = orderRepository.findByStore_PublicCodeAndTableIdAndSessionId(publicCode, tableId, + sessionId); // orderId 기준으로 바로 변환 return userOrders.stream() @@ -118,21 +119,24 @@ public List getOrderItemsGroupByOrderId( .toList(); } - - private static void parameterValidation(String publicCode, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { + private static void parameterValidation(String publicCode, Long tableId, + OrderCreateRequestDto orderCreateRequestDto) { if (publicCode == null || tableId == null || orderCreateRequestDto == null) { - throw new OrderParameterEmptyException(); + throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getItems() == null || orderCreateRequestDto.getItems().isEmpty()) { - throw new OrderItemsEmptyException(); + throw new OrderItemsEmptyException(); } - if (orderCreateRequestDto.getDepositorName() == null || orderCreateRequestDto.getDepositorName().trim().isEmpty()) { - throw new OrderParameterEmptyException(); + if (orderCreateRequestDto.getDepositorName() == null || orderCreateRequestDto.getDepositorName() + .trim() + .isEmpty()) { + throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getDepositorName().length() > 20) { - throw new DepositorNameTooLongException(); + throw new DepositorNameTooLongException(); } } + private String generateOrderSignature(String storeId, Long tableId, List items) { String cartString = items.stream() .sorted((a, b) -> a.getMenuId().compareTo(b.getMenuId())) // 메뉴 ID 기준 정렬 From 24ce6591ceccb5885f94c61d201b5354725b5ec3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:01:51 +0900 Subject: [PATCH 546/685] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=98=88=EC=99=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/exception/StoreKeywordEmptyException.java | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java deleted file mode 100644 index e69ad30b..00000000 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nowait.domaincorerdb.store.exception; - -import com.nowait.common.exception.ErrorMessage; - -public class StoreKeywordEmptyException extends RuntimeException { - public StoreKeywordEmptyException() { - super(ErrorMessage.STORE_PARAMETER_EMPTY.getMessage()); - } -} From 4642383cc816411b95f02bc49df198fab3cd863d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:02:12 +0900 Subject: [PATCH 547/685] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=9D=BC=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/nowait/common/exception/ErrorMessage.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index be45f308..f1e69dc3 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -38,7 +38,6 @@ public enum ErrorMessage { NOT_FOUND_BOOKMARK("북마크를 찾을 수 없습니다", "bookmark003"), ALREADY_DELETED_BOOKMARK("이미 삭제된 북마크입니다.", "bookmark004"), - // menu MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"), MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu002"), @@ -69,8 +68,8 @@ public enum ErrorMessage { MENU_COUNTER_UPDATE("메뉴 카운터 업데이트 실패", "statistics002"), // image - IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), - IMAGE_FILE_NOT_FOUND("이미지 파일을 업로드 해주세요", "image001"), + IMAGE_FILE_EMPTY("업로드 된 이미지 파일이 없습니다.", "image001"), + IMAGE_FILE_NOT_FOUND("DB에 해당 이미지 메타데이터가 존재하지 않습니다.", "image001"), // search SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"); From 0249ef210535db8c21368c7b574a36680975cc25 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:02:28 +0900 Subject: [PATCH 548/685] =?UTF-8?q?feat:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 282 +++++++++++++++--- 1 file changed, 242 insertions(+), 40 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index c87fe1be..2929ba80 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -1,9 +1,7 @@ package com.nowait.applicationadmin.exception; import static com.nowait.common.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.FORBIDDEN; -import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.UNAUTHORIZED; import java.util.Map; @@ -27,22 +25,42 @@ import com.nowait.discord.service.DiscordAlarmService; import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuImageEmptyException; +import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; +import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.store.exception.StoreDeleteUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreImageEmptyException; +import com.nowait.domaincorerdb.store.exception.StoreImageNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; import com.nowait.domaincorerdb.store.exception.StoreUpdateUnauthorizedException; import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentCreationUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentUpdateUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentViewUnauthorizedException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincoreredis.rank.exception.MenuCounterUpdateException; import io.swagger.v3.oas.annotations.Hidden; import lombok.RequiredArgsConstructor; @@ -134,20 +152,17 @@ public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest r return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); } - @ResponseStatus(BAD_REQUEST) - @ExceptionHandler(OrderParameterEmptyException.class) - public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e, WebRequest request) { - alarm(e, request); - log.error("orderParameterEmptyException", e); - return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); - } + + /** + * 주문 관련 예외 처리 + */ @ResponseStatus(BAD_REQUEST) - @ExceptionHandler(OrderItemsEmptyException.class) - public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { + @ExceptionHandler(DepositorNameTooLongException.class) + public ErrorResponse depositorNameTooLongException(DepositorNameTooLongException e, WebRequest request) { alarm(e, request); - log.error("orderItemsEmptyException", e); - return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); + log.error("depositorNameTooLongException", e); + return new ErrorResponse(e.getMessage(), DEPOSITOR_NAME_TOO_LONG.getCode()); } @ResponseStatus(BAD_REQUEST) @@ -158,12 +173,12 @@ public ErrorResponse duplicateOrderException(DuplicateOrderException e, WebReque return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); } - @ResponseStatus(FORBIDDEN) - @ExceptionHandler(OrderViewUnauthorizedException.class) - public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e, WebRequest request) { + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(OrderItemsEmptyException.class) + public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { alarm(e, request); - log.error("orderViewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), ORDER_VIEW_UNAUTHORIZED.getCode()); + log.error("orderItemsEmptyException", e); + return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); } @ResponseStatus(NOT_FOUND) @@ -174,6 +189,14 @@ public ErrorResponse orderNotFoundException(OrderNotFoundException e, WebRequest return new ErrorResponse(e.getMessage(), ORDER_NOT_FOUND.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(OrderParameterEmptyException.class) + public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e, WebRequest request) { + alarm(e, request); + log.error("orderParameterEmptyException", e); + return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(OrderUpdateUnauthorizedException.class) public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedException e, WebRequest request) { @@ -182,6 +205,34 @@ public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedExc return new ErrorResponse(e.getMessage(), ORDER_UPDATE_UNAUTHORIZED.getCode()); } + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(OrderViewUnauthorizedException.class) + public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("orderViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), ORDER_VIEW_UNAUTHORIZED.getCode()); + } + + /** + * 예약 관련 예외 처리 + */ + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(DuplicateReservationException.class) + public ErrorResponse duplicateReservationException(DuplicateReservationException e, WebRequest request) { + alarm(e, request); + log.error("duplicateReservationException", e); + return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(ReservationAddUnauthorizedException.class) + public ErrorResponse reservationAddUnauthorizedException(ReservationAddUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("reservationAddUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ADD_UNAUTHORIZED.getCode()); + } + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ReservationNotFoundException.class) public ErrorResponse reservationNotFoundException(ReservationNotFoundException e, WebRequest request) { @@ -190,12 +241,12 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } - @ResponseStatus(FORBIDDEN) - @ExceptionHandler(ReservationViewUnauthorizedException.class) - public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e, WebRequest request) { + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(ReservationNumberIssueFailException.class) + public ErrorResponse reservationNumberIssueFailException(ReservationNumberIssueFailException e, WebRequest request) { alarm(e, request); - log.error("reservation_viewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); + log.error("reservationNumberIssueFailException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_NUMBER_ISSUE_FAIL.getCode()); } @ResponseStatus(FORBIDDEN) @@ -206,6 +257,26 @@ public ErrorResponse reservationUpdateUnauthorizedException(ReservationUpdateUna return new ErrorResponse(e.getMessage(), RESERVATION_UPDATE_UNAUTHORIZED.getCode()); } + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(ReservationViewUnauthorizedException.class) + public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("reservation_viewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(UserWaitingLimitExceededException.class) + public ErrorResponse userWaitingLimitExceededException(UserWaitingLimitExceededException e, WebRequest request) { + alarm(e, request); + log.error("userWaitingLimitExceededException", e); + return new ErrorResponse(e.getMessage(), USER_WAITING_LIMIT_EXCEEDED.getCode()); + } + + + /** + * 메뉴 관련 예외 처리 + */ @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuCreationUnauthorizedException.class) public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e, WebRequest request) { @@ -215,11 +286,35 @@ public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedE } @ResponseStatus(FORBIDDEN) - @ExceptionHandler(MenuViewUnauthorizedException.class) - public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e, WebRequest request) { + @ExceptionHandler(MenuDeleteUnauthorizedException.class) + public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("menuViewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode()); + log.error("menuDeleteUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuImageEmptyException.class) + public ErrorResponse menuImageEmptyException(MenuImageEmptyException e, WebRequest request) { + alarm(e, request); + log.error("menuImageEmptyException", e); + return new ErrorResponse(e.getMessage(), IMAGE_FILE_EMPTY.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(MenuNotFoundException.class) + public ErrorResponse menuNotFoundException(MenuNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("menuNotFoundException", e); + return new ErrorResponse(e.getMessage(), MENU_NOT_FOUND.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuParamEmptyException.class) + public ErrorResponse menuParamEmptyException(MenuParamEmptyException e, WebRequest request) { + alarm(e, request); + log.error("menuParamEmptyException", e); + return new ErrorResponse(e.getMessage(), MENU_PARAMETER_EMPTY.getCode()); } @ResponseStatus(FORBIDDEN) @@ -231,19 +326,55 @@ public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedExcep } @ResponseStatus(FORBIDDEN) - @ExceptionHandler(MenuDeleteUnauthorizedException.class) - public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e, WebRequest request) { + @ExceptionHandler(MenuViewUnauthorizedException.class) + public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("menuDeleteUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); + log.error("menuViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode()); } + + /** + * 주점 관련 예외 처리 + */ @ResponseStatus(FORBIDDEN) - @ExceptionHandler(StoreViewUnauthorizedException.class) - public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e, WebRequest request) { + @ExceptionHandler(StoreDeleteUnauthorizedException.class) + public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("storeViewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), STORE_VIEW_UNAUTHORIZED.getCode()); + log.error("storeDeleteUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_DELETE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(StoreImageEmptyException.class) + public ErrorResponse storeImageEmptyException(StoreImageEmptyException e, WebRequest request) { + alarm(e, request); + log.error("storeImageEmptyException", e); + return new ErrorResponse(e.getMessage(), IMAGE_FILE_EMPTY.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StoreImageNotFoundException.class) + public ErrorResponse storeImageNotFoundException(StoreImageNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("storeImageNotFoundException", e); + return new ErrorResponse(e.getMessage(), IMAGE_FILE_NOT_FOUND.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StoreNotFoundException.class) + public ErrorResponse storeNotFoundException(StoreNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("storeNotFoundException", e); + return new ErrorResponse(e.getMessage(), STORE_NOT_FOUND.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(StoreParamEmptyException.class) + public ErrorResponse storeParamEmptyException(StoreParamEmptyException e, WebRequest request) { + alarm(e, request); + log.error("storeParamEmptyException", e); + return new ErrorResponse(e.getMessage(), STORE_PARAMETER_EMPTY.getCode()); } @ResponseStatus(FORBIDDEN) @@ -255,13 +386,84 @@ public ErrorResponse storeUpdateUnauthorizedException(StoreUpdateUnauthorizedExc } @ResponseStatus(FORBIDDEN) - @ExceptionHandler(StoreDeleteUnauthorizedException.class) - public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e, WebRequest request) { + @ExceptionHandler(StoreViewUnauthorizedException.class) + public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("storeDeleteUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), STORE_DELETE_UNAUTHORIZED.getCode()); + log.error("storeViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StoreWaitingDisabledException.class) + public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException e, WebRequest request) { + alarm(e, request); + log.error("storeWaitingDisabledException", e); + return new ErrorResponse(e.getMessage(), STORE_WAITING_DISABLED.getCode()); + } + + /** + * 결제 수단 관련 예외처리 + */ + @ResponseStatus(CONFLICT) + @ExceptionHandler(StorePaymentAlreadyExistsException.class) + public ErrorResponse storePaymentAlreadyExistsException(StorePaymentAlreadyExistsException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentAlreadyExistsException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_ALREADY_EXISTS.getCode()); } + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StorePaymentCreationUnauthorizedException.class) + public ErrorResponse storePaymentCreationUnauthorizedException(StorePaymentCreationUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentCreationUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_CREATION_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StorePaymentNotFoundException.class) + public ErrorResponse storePaymentNotFoundException(StorePaymentNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentNotFoundException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_NOT_FOUND.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(StorePaymentParamEmptyException.class) + public ErrorResponse storePaymentParamEmptyException(StorePaymentParamEmptyException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentParamEmptyException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_PARAMETER_EMPTY.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StorePaymentUpdateUnauthorizedException.class) + public ErrorResponse storePaymentUpdateUnauthorizedException(StorePaymentUpdateUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentUpdateUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_UPDATE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StorePaymentViewUnauthorizedException.class) + public ErrorResponse storePaymentViewUnauthorizedException(StorePaymentViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_VIEW_UNAUTHORIZED.getCode()); + } + + /** + * redis 관련 예외처리 + */ + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(MenuCounterUpdateException.class) + public ErrorResponse menuCounterUpdateException(MenuCounterUpdateException e, WebRequest request) { + alarm(e, request); + log.error("menuCounterUpdateException", e); + return new ErrorResponse(e.getMessage(), MENU_COUNTER_UPDATE.getCode()); + } + + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() From 6c078a35e655002b0f408ec384fc0210365a3a12 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:52:23 +0900 Subject: [PATCH 549/685] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EB=B0=8F=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/common/exception/ErrorMessage.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index f1e69dc3..4db017de 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -12,7 +12,7 @@ public enum ErrorMessage { DOES_NOT_MATCH_REFRESH_TOKEN("기존 리프레시 토큰이 일치하지 않습니다.", "token002"), // user - NOTFOUND_USER("저장된 사용자 정보가 없습니다.", "user001"), + NOT_FOUND_USER("저장된 사용자 정보가 없습니다.", "user001"), //order ORDER_PARAMETER_EMPTY("주문 생성 시 파라미터 정보가 없습니다.", "order001"), @@ -22,6 +22,8 @@ public enum ErrorMessage { ORDER_VIEW_UNAUTHORIZED("주문 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "order005"), ORDER_NOT_FOUND("해당 주문을 찾을 수 없습니다.", "order006"), ORDER_UPDATE_UNAUTHORIZED("주문 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "order007"), + ORDER_ALREADY_CANCELLED("이미 취소된 주문입니다.", "order008"), + INVALID_ORDER_STATUS_TRANSITION("유효하지 않은 주문 상태 변경입니다. (현재: %s, 요청: %s)", "order009"), //reservation NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"), @@ -59,9 +61,9 @@ public enum ErrorMessage { STORE_PAYMENT_NOT_FOUND("해당 주점 결제 정보를 찾을 수 없습니다.", "storePayment002"), STORE_PAYMENT_VIEW_UNAUTHORIZED("주점 결제 정보 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment003"), STORE_PAYMENT_CREATION_UNAUTHORIZED("주점 결제 정보 생성 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), - STORE_PAYMENT_UPDATE_UNAUTHORIZED("주점 결제 정보 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), - STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), - STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment006"), + STORE_PAYMENT_UPDATE_UNAUTHORIZED("주점 결제 정보 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), + STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment006"), + STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment007"), // Statistics STATISTIC_VIEW_UNAUTHORIZED("통계 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "statistics001"), @@ -69,7 +71,7 @@ public enum ErrorMessage { // image IMAGE_FILE_EMPTY("업로드 된 이미지 파일이 없습니다.", "image001"), - IMAGE_FILE_NOT_FOUND("DB에 해당 이미지 메타데이터가 존재하지 않습니다.", "image001"), + IMAGE_FILE_NOT_FOUND("DB에 해당 이미지 메타데이터가 존재하지 않습니다.", "image002"), // search SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"); @@ -89,4 +91,8 @@ public String getMessage() { public String getCode() { return code; } + + public String format(Object... args) { + return String.format(message, args); + } } From ccbf25862070130ec5bab823b20a0305f03facd9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:52:55 +0900 Subject: [PATCH 550/685] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=9D=BC=EA=B4=80=EC=84=B1=20=EC=9E=88?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 11 ++++++++++- .../exception/GlobalExceptionHandler.java | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 2929ba80..79f3ae01 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -32,6 +32,7 @@ import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; +import com.nowait.domaincorerdb.order.exception.OrderAlreadyCancelledException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; @@ -149,7 +150,7 @@ public ErrorResponse handleMultipartException(MultipartException e, WebRequest r public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { alarm(e, request); log.error("userNotFoundException", e); - return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); + return new ErrorResponse(e.getMessage(), NOT_FOUND_USER.getCode()); } @@ -173,6 +174,14 @@ public ErrorResponse duplicateOrderException(DuplicateOrderException e, WebReque return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(OrderAlreadyCancelledException.class) + public ErrorResponse orderAlreadyCancelledException(OrderAlreadyCancelledException e, WebRequest request) { + alarm(e, request); + log.error("orderAlreadyCancelledException", e); + return new ErrorResponse(e.getMessage(), ORDER_ALREADY_CANCELLED.getCode()); + } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(OrderItemsEmptyException.class) public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 9b4c5f4d..cf09ca46 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -172,7 +172,7 @@ public ErrorResponse handleAlreadyDeleteBookmark( public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { alarm(e, request); log.error("userNotFoundException", e); - return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); + return new ErrorResponse(e.getMessage(), NOT_FOUND_USER.getCode()); } @ResponseStatus(BAD_REQUEST) From 86f66eb5da34622ea3116314ab1977bd30d61d14 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:53:38 +0900 Subject: [PATCH 551/685] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=98=88=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InvalidOrderStatusTransitionException.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java new file mode 100644 index 00000000..571c9b26 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; +import com.nowait.domaincorerdb.order.entity.OrderStatus; + +public class InvalidOrderStatusTransitionException extends RuntimeException { + public InvalidOrderStatusTransitionException(OrderStatus current, OrderStatus target) { + super(ErrorMessage.INVALID_ORDER_STATUS_TRANSITION.format(current, target)); + } +} From 09f161f3b58e6ab18d87528af17d84fe08bd6b57 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:53:51 +0900 Subject: [PATCH 552/685] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/exception/OrderAlreadyCancelledException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java new file mode 100644 index 00000000..2d650670 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class OrderAlreadyCancelledException extends RuntimeException { + public OrderAlreadyCancelledException() { + super(ErrorMessage.ORDER_ALREADY_CANCELLED.getMessage()); + } +} From f82e07c6e3be47376d2ec2b0e91815ee5f6aa32e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:54:42 +0900 Subject: [PATCH 553/685] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0=20-=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B6=8C=ED=95=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?-=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/service/OrderService.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index cfd26222..aaa34365 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -46,11 +46,10 @@ public class OrderService { @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); storeRepository.findByStoreIdAndDeletedFalse(storeId).orElseThrow(StoreNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new OrderViewUnauthorizedException(); - } + + validateViewAuthorization(user, storeId); LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); LocalDateTime startDateTime = today.atStartOfDay(); @@ -64,11 +63,12 @@ public List findAllOrders(Long storeId, MemberDetails memberDe @Transactional public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { - throw new OrderUpdateUnauthorizedException(); - } + Long storeId = userOrder.getStore().getStoreId(); + + validateUpdateAuthorization(user, storeId); + userOrder.updateStatus(newStatus); if (OrderStatus.COOKED.equals(newStatus)) { @@ -92,12 +92,10 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus @Transactional public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest cancelOrderRequest, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { - throw new OrderUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, userOrder.getStore().getStoreId()); userOrder.cancelOrder(); @@ -115,25 +113,40 @@ public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest @Transactional(readOnly = true) public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails, LocalDate date) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new OrderViewUnauthorizedException(); - } + validateViewAuthorization(user, storeId); return statisticCustomRepository.findSalesSumByStoreId(storeId, date); } + // 현재는 사용하지 않음. 향후 관리자 통계 페이지 확장 시 활용 가능 @Transactional(readOnly = true) public List getTop5StoresBySalesToday(MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + validateUpdateAuthorization(user, storeId); + + return statisticCustomRepository.getTop4PlusMine(storeId); + } + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { throw new OrderViewUnauthorizedException(); } + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new OrderUpdateUnauthorizedException(); + } + } - return statisticCustomRepository.getTop4PlusMine(storeId); + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } From 8610b76e2ae5e49aea146a8ce87489995bcd0d33 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:54:55 +0900 Subject: [PATCH 554/685] =?UTF-8?q?chore:=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=9D=BC=EA=B4=80=EC=84=B1=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/user/exception/UserNotFoundException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java index aef89ab3..ec742363 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java @@ -4,6 +4,6 @@ public class UserNotFoundException extends RuntimeException { public UserNotFoundException() { - super(ErrorMessage.NOTFOUND_USER.getMessage()); + super(ErrorMessage.NOT_FOUND_USER.getMessage()); } } From 964c7cd63a3025df968c31a9b450ccacd2c4b194 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 14:55:10 +0900 Subject: [PATCH 555/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincorerdb/order/entity/UserOrder.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java index 54fd36a9..c26ac87e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java @@ -4,6 +4,8 @@ import java.util.List; import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; +import com.nowait.domaincorerdb.order.exception.InvalidOrderStatusTransitionException; +import com.nowait.domaincorerdb.order.exception.OrderAlreadyCancelledException; import com.nowait.domaincorerdb.store.entity.Store; import jakarta.persistence.CascadeType; @@ -62,14 +64,25 @@ public class UserOrder extends BaseTimeEntity { private Integer totalPrice; public void updateStatus(OrderStatus newStatus) { + if (!isValidTransition(this.status, newStatus)) { + throw new InvalidOrderStatusTransitionException(this.status, newStatus); + } this.status = newStatus; } public void cancelOrder() { if (this.status == OrderStatus.CANCELLED) { - return; + throw new OrderAlreadyCancelledException(); } this.status = OrderStatus.CANCELLED; } + private boolean isValidTransition(OrderStatus current, OrderStatus target) { + return switch (current) { + case WAITING_FOR_PAYMENT, COOKED -> target == OrderStatus.COOKING || target == OrderStatus.CANCELLED; + case COOKING -> target == OrderStatus.COOKED || target == OrderStatus.CANCELLED; + case CANCELLED -> false; + }; + } + } From f11504eab6d2e046798b06077e6f66cbf7df3e93 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 15:14:05 +0900 Subject: [PATCH 556/685] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index fb461266..45e8149a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -26,6 +26,8 @@ import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; @@ -56,10 +58,8 @@ public class ReservationService { //TODO 성능 비교를 위해 남겨둔 로직 @Transactional(readOnly = true) public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new ReservationViewUnauthorizedException(); - } + User user = getUser(memberDetails); + validateViewAuthorization(user, storeId); List reservations = reservationRepository.findAllByStore_StoreIdOrderByRequestedAtAsc(storeId); // 상태별 카운트 집계 @@ -93,13 +93,11 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem @Transactional public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Reservation reservation = reservationRepository.findById(reservationId) .orElseThrow(ReservationNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId() - .equals(reservation.getStore().getStoreId())) { - throw new ReservationUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, reservation.getStore().getStoreId()); + reservation.markUpdated(LocalDateTime.now(), requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } @@ -194,7 +192,8 @@ public List getAllWaitingUserDetails(Long storeId) { // 완료 or 취소 처리된 대기 리스트 조회 @Transactional(readOnly = true) public List getCompletedWaitingUserDetails(Long storeId, MemberDetails memberDetails) { - authorize(storeId, memberDetails); + User user = getUser(memberDetails); + validateViewAuthorization(user, storeId); List reservations = findTodayWaiting(storeId); return reservations.stream() @@ -212,7 +211,8 @@ public List getCompletedWaitingUserDetails(Long storeId, Me public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, ReservationStatus newStatus) { - authorize(storeId, member); + User user = getUser(member); + validateUpdateAuthorization(user, storeId); String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; @@ -350,13 +350,22 @@ private List findTodayWaiting(Long storeId) { } // 사용자 인증 - private User authorize(Long storeId, MemberDetails member) { - User u = userRepository.findById(member.getId()) - .orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(u.getRole()) && !storeId.equals(u.getStoreId())) { + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { throw new ReservationViewUnauthorizedException(); } - return u; + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new ReservationUpdateUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } From 537422f7762d4345872ffbbc151f48a0f0e3f73a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 15:50:28 +0900 Subject: [PATCH 557/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 45 +++++++++++++++++++ .../nowait/common/exception/ErrorMessage.java | 7 +++ .../InvalidReservationParameterException.java | 9 ++++ ...dReservationStatusTransitionException.java | 10 +++++ .../ReservationAlreadyCancelledException.java | 9 ++++ .../ReservationAlreadyConfirmedException.java | 9 ++++ ...ReservationDataInconsistencyException.java | 10 +++++ ...UnsupportedReservationStatusException.java | 10 +++++ 8 files changed, 109 insertions(+) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 79f3ae01..5c8de093 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -39,11 +39,16 @@ import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyCancelledException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyConfirmedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.UnsupportedReservationStatusException; import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.store.exception.StoreDeleteUnauthorizedException; import com.nowait.domaincorerdb.store.exception.StoreImageEmptyException; @@ -234,6 +239,22 @@ public ErrorResponse duplicateReservationException(DuplicateReservationException return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(InvalidReservationParameterException.class) + public ErrorResponse invalidReservationParameterException(InvalidReservationParameterException e, WebRequest request) { + alarm(e, request); + log.error("invalidReservationParameterException", e); + return new ErrorResponse(e.getMessage(), INVALID_RESERVATION_PARAMETER.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(InvalidReservationStatusTransitionException.class) + public ErrorResponse invalidReservationStatusTransitionException(InvalidReservationStatusTransitionException e, WebRequest request) { + alarm(e, request); + log.error("invalidReservationStatusTransitionException", e); + return new ErrorResponse(e.getMessage(), INVALID_RESERVATION_STATUS_TRANSITION.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(ReservationAddUnauthorizedException.class) public ErrorResponse reservationAddUnauthorizedException(ReservationAddUnauthorizedException e, WebRequest request) { @@ -242,6 +263,22 @@ public ErrorResponse reservationAddUnauthorizedException(ReservationAddUnauthori return new ErrorResponse(e.getMessage(), RESERVATION_ADD_UNAUTHORIZED.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(ReservationAlreadyCancelledException.class) + public ErrorResponse reservationAlreadyCancelledException(ReservationAlreadyCancelledException e, WebRequest request) { + alarm(e, request); + log.error("reservationAlreadyCancelledException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ALREADY_CANCELLED.getCode()); + } + + @ResponseStatus(CONFLICT) + @ExceptionHandler(ReservationAlreadyConfirmedException.class) + public ErrorResponse reservationAlreadyConfirmedException(ReservationAlreadyConfirmedException e, WebRequest request) { + alarm(e, request); + log.error("reservationAlreadyConfirmedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ALREADY_CONFIRMED.getCode()); + } + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ReservationNotFoundException.class) public ErrorResponse reservationNotFoundException(ReservationNotFoundException e, WebRequest request) { @@ -274,6 +311,14 @@ public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnautho return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(UnsupportedReservationStatusException.class) + public ErrorResponse unsupportedReservationStatusException(UnsupportedReservationStatusException e, WebRequest request) { + alarm(e, request); + log.error("unsupportedReservationStatusException", e); + return new ErrorResponse(e.getMessage(), UNSUPPORTED_RESERVATION_STATUS.getCode()); + } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(UserWaitingLimitExceededException.class) public ErrorResponse userWaitingLimitExceededException(UserWaitingLimitExceededException e, WebRequest request) { diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 4db017de..9edb9521 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -33,6 +33,13 @@ public enum ErrorMessage { USER_WAITING_LIMIT_EXCEEDED("유저당 웨이팅 가능 개수(3개)를 초과했습니다.", "reservation005"), RESERVATION_NUMBER_ISSUE_FAIL("예약 번호 발급에 실패했습니다.", "reservation006"), RESERVATION_ADD_UNAUTHORIZED("MANAGER는 예약 대기를 할 수 없습니다.", "reservation007"), + INVALID_RESERVATION_STATUS_TRANSITION("유효하지 않은 예약 상태 변경입니다. (현재: %s, 요청: %s)", "reservation008"), + RESERVATION_ALREADY_CONFIRMED("이미 확정된 예약입니다.", "reservation009"), + RESERVATION_ALREADY_CANCELLED("이미 취소된 예약입니다.", "reservation010"), + RESERVATION_DATA_INCONSISTENCY("예약 데이터가 Redis와 DB 간 일치하지 않습니다. (%s)", "reservation011"), + UNSUPPORTED_RESERVATION_STATUS("지원하지 않는 예약 상태입니다: %s", "reservation012"), + INVALID_RESERVATION_PARAMETER("잘못된 예약 요청 파라미터입니다. (%s)", "reservation013"), + // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java new file mode 100644 index 00000000..2db8dd4a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class InvalidReservationParameterException extends RuntimeException { + public InvalidReservationParameterException(String details) { + super(ErrorMessage.INVALID_RESERVATION_PARAMETER.format(details)); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java new file mode 100644 index 00000000..cc6fe02c --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.exception.ErrorMessage; + +public class InvalidReservationStatusTransitionException extends RuntimeException { + public InvalidReservationStatusTransitionException(ReservationStatus current, ReservationStatus target) { + super(ErrorMessage.INVALID_RESERVATION_STATUS_TRANSITION.format(current, target)); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java new file mode 100644 index 00000000..1d2a7666 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationAlreadyCancelledException extends RuntimeException { + public ReservationAlreadyCancelledException() { + super(ErrorMessage.RESERVATION_ALREADY_CANCELLED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java new file mode 100644 index 00000000..724b1678 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationAlreadyConfirmedException extends RuntimeException { + public ReservationAlreadyConfirmedException() { + super(ErrorMessage.RESERVATION_ALREADY_CONFIRMED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java new file mode 100644 index 00000000..37cd13da --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationDataInconsistencyException extends RuntimeException { + public ReservationDataInconsistencyException(String details) { + super(ErrorMessage.RESERVATION_DATA_INCONSISTENCY.format(details)); + } +} + diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java new file mode 100644 index 00000000..98295756 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.exception.ErrorMessage; + +public class UnsupportedReservationStatusException extends RuntimeException { + public UnsupportedReservationStatusException(ReservationStatus status) { + super(ErrorMessage.UNSUPPORTED_RESERVATION_STATUS.format(status)); + } +} From 09ee01d57511bc5d23c41cae17ae0238ef21017b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 15:50:36 +0900 Subject: [PATCH 558/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 35 +++++++++++++++---- .../reservation/entity/Reservation.java | 32 +++++++++++++++-- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 45e8149a..eae6fdf8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -26,12 +26,14 @@ import com.nowait.applicationadmin.reservation.dto.WaitingUserResponse; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; -import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; -import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; +import com.nowait.domaincorerdb.reservation.exception.ReservationDataInconsistencyException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.UnsupportedReservationStatusException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; @@ -214,6 +216,10 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me User user = getUser(member); validateUpdateAuthorization(user, storeId); + if (userId == null || userId.isBlank()) { + throw new InvalidReservationParameterException("userId 값이 비어있습니다."); + } + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; // Redis에서 상태·score·partySize·calledAt 조회 @@ -222,6 +228,17 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me Double score = redisTemplate.opsForZSet().score(queueKey, userId); Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + if (partySize == null || partySize <= 0) { + throw new InvalidReservationParameterException("partySize가 유효하지 않습니다. (storeId=" + storeId + ", userId=" + userId + ")"); + } + + if (reservationNumber == null || currStatus == null) { + throw new ReservationDataInconsistencyException( + String.format("storeId=%d, userId=%s, reservationNumber=%s, status=%s, partySize=%s", + storeId, userId, reservationNumber, currStatus, partySize) + ); + } + LocalDateTime requestedAt = score != null ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() : LocalDateTime.now(); @@ -230,7 +247,9 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me switch (newStatus) { case CALLING: if (!ReservationStatus.WAITING.name().equals(currStatus)) { - throw new IllegalStateException("WAITING 상태에서만 CALLING 가능합니다."); + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CALLING + ); } waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); waitingRedisRepository.setWaitingCalledAt(storeId, userId, @@ -294,7 +313,9 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me List.of(ReservationStatus.CANCELLED), start, end - ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); + ).orElseThrow(() -> new ReservationDataInconsistencyException( + String.format("취소된 예약이 DB에 존재하지 않습니다. (storeId=%d, userId=%s)", storeId, userId) + )); existing.markUpdated(LocalDateTime.now(ZoneId.of("Asia/Seoul")), ReservationStatus.CONFIRMED); Reservation saved = reservationRepository.save(existing); @@ -304,7 +325,9 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me case CANCELLED: if (!(ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus))) { - throw new IllegalStateException("WAITING/CALLING 상태에서만 취소 가능합니다."); + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CANCELLED + ); } if (reservationNumber != null) { @@ -329,7 +352,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me return EntryStatusResponseDto.fromEntity(saved); default: - throw new IllegalArgumentException("지원하지 않는 상태: " + newStatus); + throw new UnsupportedReservationStatusException(newStatus); } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index da5ec2bf..4874444e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -3,6 +3,9 @@ import java.time.LocalDateTime; import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyCancelledException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyConfirmedException; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.user.entity.User; @@ -59,8 +62,31 @@ public class Reservation { @Column(name = "party_size", nullable = false) private Integer partySize; - public void markUpdated(LocalDateTime ts, ReservationStatus status) { - this.status = status; - this.updatedAt = ts; + public void markUpdated(LocalDateTime updatedAt, ReservationStatus newStatus) { + if (this.status == newStatus) { + switch (newStatus) { + case CONFIRMED -> throw new ReservationAlreadyConfirmedException(); + case CANCELLED -> throw new ReservationAlreadyCancelledException(); + default -> {} + } + } + + if (!isValidTransition(this.status, newStatus)) { + throw new InvalidReservationStatusTransitionException(this.status, newStatus); + } + this.status = newStatus; + this.updatedAt = updatedAt; + } + + private boolean isValidTransition(ReservationStatus current, ReservationStatus target) { + return switch (current) { + case WAITING -> target == ReservationStatus.CALLING + || target == ReservationStatus.CONFIRMED + || target == ReservationStatus.CANCELLED; + case CALLING -> target == ReservationStatus.CONFIRMED + || target == ReservationStatus.CANCELLED; + case CONFIRMED -> target == ReservationStatus.CANCELLED; + case CANCELLED -> false; + }; } } From 0569690fb50a5bb1afc833e436f9d4b0f5ed56d9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 16:46:55 +0900 Subject: [PATCH 559/685] =?UTF-8?q?refactor:=20RESERVATION=5FDATA=5FINCONS?= =?UTF-8?q?ISTENCY=20redis=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nowait/common/exception/ErrorMessage.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 9edb9521..1f61d392 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -36,10 +36,11 @@ public enum ErrorMessage { INVALID_RESERVATION_STATUS_TRANSITION("유효하지 않은 예약 상태 변경입니다. (현재: %s, 요청: %s)", "reservation008"), RESERVATION_ALREADY_CONFIRMED("이미 확정된 예약입니다.", "reservation009"), RESERVATION_ALREADY_CANCELLED("이미 취소된 예약입니다.", "reservation010"), - RESERVATION_DATA_INCONSISTENCY("예약 데이터가 Redis와 DB 간 일치하지 않습니다. (%s)", "reservation011"), - UNSUPPORTED_RESERVATION_STATUS("지원하지 않는 예약 상태입니다: %s", "reservation012"), - INVALID_RESERVATION_PARAMETER("잘못된 예약 요청 파라미터입니다. (%s)", "reservation013"), + UNSUPPORTED_RESERVATION_STATUS("지원하지 않는 예약 상태입니다: %s", "reservation011"), + INVALID_RESERVATION_PARAMETER("잘못된 예약 요청 파라미터입니다. (%s)", "reservation012"), + // redis + RESERVATION_DATA_INCONSISTENCY("예약 데이터가 Redis와 DB 간 일치하지 않습니다. (%s)", "redis001"), // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), From 78afb19705e300c039a9b89041cd7510211bcca2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 16:47:12 +0900 Subject: [PATCH 560/685] =?UTF-8?q?feat:=20buildReservationUserKey=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincoreredis/common/util/RedisKeyUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index f40d5690..1f81d2f8 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -76,6 +76,10 @@ public static String buildReservationNumberKey(Long storeId) { return String.format("reservation:number:%d", storeId); } + public static String buildReservationUserKey(Long storeId) { + return String.format("reservation:user:%d", storeId); + } + /** * 대기 호출 시각(hash)에 사용할 키 접두사 */ From 342c8ccdf5248949b3f1a73f68a4030a44fa63dc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 16:47:40 +0900 Subject: [PATCH 561/685] =?UTF-8?q?feat:=20=EC=98=88=EC=95=BD=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EA=B8=B0=EB=B0=98=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 18 ++ .../service/ReservationService.java | 156 +++++++++++++++++- 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index d962de4c..3d5eaf74 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -96,4 +96,22 @@ public ResponseEntity updateEntry( )); } + @PatchMapping("/admin/{storeId}/{reservationNumber}") + @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") + @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") + public ResponseEntity updateEntryWithReservationNumber( + @PathVariable Long storeId, + @PathVariable String reservationNumber, + @RequestBody ReservationStatusRequest request, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + EntryStatusResponseDto response = reservationService.processEntryStatusByReservationNumber(storeId, reservationNumber, memberDetails, request.getStatus()); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + )); + } + } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index eae6fdf8..4d728c14 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -29,7 +29,7 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; -import com.nowait.domaincorerdb.reservation.exception.ReservationDataInconsistencyException; +import com.nowait.domaincoreredis.reservation.exception.ReservationDataInconsistencyException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; @@ -233,7 +233,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me } if (reservationNumber == null || currStatus == null) { - throw new ReservationDataInconsistencyException( + new ReservationDataInconsistencyException( String.format("storeId=%d, userId=%s, reservationNumber=%s, status=%s, partySize=%s", storeId, userId, reservationNumber, currStatus, partySize) ); @@ -356,6 +356,158 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me } } + + @Transactional + public EntryStatusResponseDto processEntryStatusByReservationNumber(Long storeId, String reservationNumber, + MemberDetails member, ReservationStatus newStatus) { + + User user = getUser(member); + validateUpdateAuthorization(user, storeId); + + if (reservationNumber == null || reservationNumber.isBlank()) { + throw new InvalidReservationParameterException("reservationNumber 값이 비어있습니다."); + } + + // Redis에서 userId 역추적 + String userId = waitingRedisRepository.getUserIdByReservationNumber(storeId, reservationNumber); + if (userId == null) { + throw new ReservationDataInconsistencyException( + String.format("storeId=%d, reservationNumber=%s 에 해당하는 userId를 찾을 수 없습니다.", storeId, reservationNumber) + ); + } + + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + + String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId); + Double score = redisTemplate.opsForZSet().score(queueKey, userId); + Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + + + if (partySize == null || partySize <= 0) { + throw new InvalidReservationParameterException("partySize가 유효하지 않습니다. (storeId=" + storeId + ", reservationNumber=" + reservationNumber + ")"); + } + + if (currStatus == null) { + throw new ReservationDataInconsistencyException( + String.format("storeId=%d, reservationNumber=%s 의 상태값을 찾을 수 없습니다.", storeId, reservationNumber) + ); + } + + LocalDateTime requestedAt = score != null + ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() + : LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + + switch (newStatus) { + case CALLING: + if (!ReservationStatus.WAITING.name().equals(currStatus)) { + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CALLING + ); + } + waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); + waitingRedisRepository.setWaitingCalledAt(storeId, userId, + now.atZone(ZoneId.of("Asia/Seoul")).toInstant().toEpochMilli()); + + return EntryStatusResponseDto.builder() + .reservationNumber(reservationNumber) + .userId(userId) + .partySize(partySize) + .userName(userRepository.getReferenceById(Long.valueOf(userId)).getNickname()) + .createdAt(requestedAt) + .status("CALLING") + .updatedAt(now) + .message("호출되었습니다.") + .build(); + + case CONFIRMED: + // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 + if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name() + .equals(currStatus)) { + + if (reservationNumber != null) { + waitingPermitLuaRepository.removeActiveMember( + userId, String.valueOf(storeId), reservationNumber + ); + } + + // 새 Reservation 생성 & 저장 + Reservation r = Reservation.builder() + .reservationNumber(reservationNumber) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.valueOf(userId))) + .partySize(partySize) + .requestedAt(requestedAt) + .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .status(ReservationStatus.valueOf(currStatus)) + .build(); + + // 호출 시각 반영 + r.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); + Reservation saved = reservationRepository.save(r); + // Redis 전부 삭제 + waitingRedisRepository.deleteWaiting(storeId, userId); + return EntryStatusResponseDto.fromEntity(saved); + } else { + if (reservationNumber != null) { + try { + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), + reservationNumber); + } catch (Exception ignore) { + } + } + + // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 + LocalDateTime start = LocalDate.now().atStartOfDay(); + LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); + Reservation existing = reservationRepository + .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( + storeId, + Long.valueOf(userId), + List.of(ReservationStatus.CANCELLED), + start, + end + ).orElseThrow(() -> new ReservationDataInconsistencyException( + String.format("취소된 예약이 DB에 존재하지 않습니다. (storeId=%d, userId=%s)", storeId, userId) + )); + + existing.markUpdated(LocalDateTime.now(ZoneId.of("Asia/Seoul")), ReservationStatus.CONFIRMED); + Reservation saved = reservationRepository.save(existing); + return EntryStatusResponseDto.fromEntity(saved); + } + + case CANCELLED: + if (!(ReservationStatus.WAITING.name().equals(currStatus) + || ReservationStatus.CALLING.name().equals(currStatus))) { + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CANCELLED + ); + } + + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); + + Reservation r = Reservation.builder() + .reservationNumber(reservationNumber) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.valueOf(userId))) + .partySize(partySize) + .requestedAt(requestedAt) + .updatedAt(now) + .status(ReservationStatus.valueOf(currStatus)) + .build(); + + r.markUpdated(now, ReservationStatus.CANCELLED); + Reservation saved = reservationRepository.save(r); + waitingRedisRepository.deleteWaiting(storeId, userId); + + return EntryStatusResponseDto.fromEntity(saved); + + default: + throw new UnsupportedReservationStatusException(newStatus); + } + } + + /** * 공통 메서드 */ From 352b561d8a0109d89876f20707a4aa022553af59 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 16:47:50 +0900 Subject: [PATCH 562/685] =?UTF-8?q?feat:=20=EC=98=88=EC=95=BD=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EA=B8=B0=EB=B0=98=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingRedisRepository.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index 0799786e..f9eb921b 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -25,7 +25,6 @@ public List> getAllWaitingWithScore(Long store return waitingSet == null ? List.of() : new ArrayList<>(waitingSet); } - public List getAllWaitingUserIds(Long storeId) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; // 0부터 -1까지: 전체 범위 @@ -33,7 +32,6 @@ public List getAllWaitingUserIds(Long storeId) { return userIds == null ? List.of() : new ArrayList<>(userIds); } - // 상태값 저장 및 변경 public void setWaitingStatus(Long storeId, String userId, String status) { String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; @@ -54,7 +52,7 @@ public Integer getWaitingPartySize(Long storeId, String userId) { return value == null ? null : Integer.valueOf(value.toString()); } - // ReservationNumber 조회 + // userId → reservationNumber 조회 public String getReservationId(Long storeId, String userId) { String status = getWaitingStatus(storeId, userId); if (!"WAITING".equals(status) && !"CALLING".equals(status)) { @@ -67,7 +65,27 @@ public String getReservationId(Long storeId, String userId) { return val != null ? val.toString() : null; } + // reservationNumber → userId 조회 + public String getUserIdByReservationNumber(Long storeId, String reservationNumber) { + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); + Object val = redisTemplate.opsForHash().get(userMapKey, reservationNumber); + return val == null ? null : val.toString(); + } + public void deleteWaiting(Long storeId, String userId) { + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); + + Object reservationNumber = redisTemplate.opsForHash().get(numberMapKey, userId); + + // userId → reservationNumber 삭제 + redisTemplate.opsForHash().delete(numberMapKey, userId); + + // reservationNumber → userId 삭제 + if (reservationNumber != null) { + redisTemplate.opsForHash().delete(userMapKey, reservationNumber); + } + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForHash().delete(statusKey, userId); @@ -77,11 +95,8 @@ public void deleteWaiting(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; redisTemplate.opsForHash().delete(partyKey, userId); - String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); - redisTemplate.opsForHash().delete(numberMapKey, userId); - - String key = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; - redisTemplate.opsForHash().delete(key, userId); + String calledAtKey = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; + redisTemplate.opsForHash().delete(calledAtKey, userId); } // 호출 시각 기록 From d51e03a25612cfd8f352238a89a0263463fe7ef1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 16:47:55 +0900 Subject: [PATCH 563/685] =?UTF-8?q?feat:=20=EC=98=88=EC=95=BD=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EA=B8=B0=EB=B0=98=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingUserRedisRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 50c84d83..d11e5a30 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -37,6 +37,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; String seqKey = RedisKeyUtils.buildReservationSeqKey(storeId); String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); String reservationId; @@ -47,7 +48,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, // 5) Hash에 저장 redisTemplate.opsForHash().put(numberMapKey, userId, reservationId); - + redisTemplate.opsForHash().put(userMapKey, reservationId, userId); // 6) 기존 partySize, status, TTL 설정 redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); From 230d2e5a596e2178058f234ea0e6607c9401f833 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 16:48:08 +0900 Subject: [PATCH 564/685] =?UTF-8?q?refactor:=20redis=20=EC=95=84=EB=9E=98?= =?UTF-8?q?=EB=A1=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ReservationDataInconsistencyException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename nowait-domain/{domain-core-rdb/src/main/java/com/nowait/domaincorerdb => domain-redis/src/main/java/com/nowait/domaincoreredis}/reservation/exception/ReservationDataInconsistencyException.java (82%) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java similarity index 82% rename from nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java rename to nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java index 37cd13da..22cc5151 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationDataInconsistencyException.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java @@ -1,4 +1,4 @@ -package com.nowait.domaincorerdb.reservation.exception; +package com.nowait.domaincoreredis.reservation.exception; import com.nowait.common.exception.ErrorMessage; From cc3f2f66e117976d56dcb68b8e81c91578a3f139 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 18:48:00 +0900 Subject: [PATCH 565/685] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/service/StoreServiceImpl.java | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index abdc5837..1f3d4706 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -15,6 +15,8 @@ import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; @@ -57,10 +59,9 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { @Transactional(readOnly = true) public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails memberDetails) { if (storeId == null) throw new StoreParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreViewUnauthorizedException(); - } + User user = getUser(memberDetails); + validateViewAuthorization(user, storeId); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -79,11 +80,11 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails mem @Override @Transactional public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (storeId == null || request == null) throw new StoreParamEmptyException(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreUpdateUnauthorizedException(); - } + + User user = getUser(memberDetails); + validateUpdateAuthorization(user, storeId); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -109,13 +110,13 @@ public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, Member @Override @Transactional public String deleteStore(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (storeId == null) { throw new StoreParamEmptyException(); } - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreDeleteUnauthorizedException(); - } + + User user = getUser(memberDetails); + validateUpdateAuthorization(user, storeId); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -134,4 +135,22 @@ public Boolean toggleActive(Long storeId) { store.toggleActive(); return store.getIsActive(); } + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new OrderViewUnauthorizedException(); + } + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new OrderUpdateUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + } } From c40461d8d9453993b6ae01f8049f5b37843a8a0e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 18:50:02 +0900 Subject: [PATCH 566/685] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationadmin/store/service/StoreServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index 1f3d4706..0892bd08 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -130,7 +130,7 @@ public String deleteStore(Long storeId, MemberDetails memberDetails) { @Transactional public Boolean toggleActive(Long storeId) { Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("해당 스토어가 존재하지 않습니다.")); + .orElseThrow(StoreNotFoundException::new); store.toggleActive(); return store.getIsActive(); From 41f64d91f9c12fe93ced05dcc8a67021914f5ff7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 19:13:12 +0900 Subject: [PATCH 567/685] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 45 +++++++++++ .../menu/service/MenuService.java | 77 ++++++++++++------- .../store/service/StoreServiceImpl.java | 4 +- .../nowait/common/exception/ErrorMessage.java | 5 ++ .../MenuAlreadyDeletedException.java | 9 +++ .../MenuCrossStoreConflictException.java | 9 +++ .../exception/MenuDuplicateIdException.java | 9 +++ .../MenuInvalidSortOrderException.java | 9 +++ .../MenuToggleUnauthorizedException.java | 9 +++ 9 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 5c8de093..1c08cc73 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -23,11 +23,16 @@ import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; import com.nowait.discord.service.DiscordAlarmService; +import com.nowait.domaincorerdb.menu.exception.MenuAlreadyDeletedException; import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuCrossStoreConflictException; import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuDuplicateIdException; import com.nowait.domaincorerdb.menu.exception.MenuImageEmptyException; +import com.nowait.domaincorerdb.menu.exception.MenuInvalidSortOrderException; import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; +import com.nowait.domaincorerdb.menu.exception.MenuToggleUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; @@ -331,6 +336,14 @@ public ErrorResponse userWaitingLimitExceededException(UserWaitingLimitExceededE /** * 메뉴 관련 예외 처리 */ + @ResponseStatus(CONFLICT) + @ExceptionHandler(MenuAlreadyDeletedException.class) + public ErrorResponse menuAlreadyDeletedException(MenuAlreadyDeletedException e, WebRequest request) { + alarm(e, request); + log.error("menuAlreadyDeletedException", e); + return new ErrorResponse(e.getMessage(), MENU_ALREADY_DELETED.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuCreationUnauthorizedException.class) public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e, WebRequest request) { @@ -339,6 +352,14 @@ public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedE return new ErrorResponse(e.getMessage(), MENU_CREATION_UNAUTHORIZED.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(MenuCrossStoreConflictException.class) + public ErrorResponse menuCrossStoreConflictException(MenuCrossStoreConflictException e, WebRequest request) { + alarm(e, request); + log.error("menuCrossStoreConflictException", e); + return new ErrorResponse(e.getMessage(), MENU_CROSS_STORE_CONFLICT.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuDeleteUnauthorizedException.class) public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e, WebRequest request) { @@ -347,6 +368,14 @@ public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedExcep return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuDuplicateIdException.class) + public ErrorResponse menuDuplicateIdException(MenuDuplicateIdException e, WebRequest request) { + alarm(e, request); + log.error("menuDuplicateIdException", e); + return new ErrorResponse(e.getMessage(), MENU_DUPLICATE_ID.getCode()); + } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MenuImageEmptyException.class) public ErrorResponse menuImageEmptyException(MenuImageEmptyException e, WebRequest request) { @@ -363,6 +392,14 @@ public ErrorResponse menuNotFoundException(MenuNotFoundException e, WebRequest r return new ErrorResponse(e.getMessage(), MENU_NOT_FOUND.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuInvalidSortOrderException.class) + public ErrorResponse menuInvalidSortOrderException(MenuInvalidSortOrderException e, WebRequest request) { + alarm(e, request); + log.error("menuInvalidSortOrderException", e); + return new ErrorResponse(e.getMessage(), MENU_INVALID_SORT_ORDER.getCode()); + } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(MenuParamEmptyException.class) public ErrorResponse menuParamEmptyException(MenuParamEmptyException e, WebRequest request) { @@ -379,6 +416,14 @@ public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedExcep return new ErrorResponse(e.getMessage(), MENU_UPDATE_UNAUTHORIZED.getCode()); } + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(MenuToggleUnauthorizedException.class) + public ErrorResponse menuToggleUnauthorizedException(MenuToggleUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("menuToggleUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_TOGGLE_UNAUTHORIZED.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuViewUnauthorizedException.class) public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e, WebRequest request) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 2bdd5589..50805e1c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -17,13 +17,18 @@ import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.entity.MenuImage; import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuCrossStoreConflictException; import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuDuplicateIdException; +import com.nowait.domaincorerdb.menu.exception.MenuInvalidSortOrderException; import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -42,12 +47,10 @@ public class MenuService { @Transactional public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) { // 사용자 정보 가져오기 - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - + User user = getUser(memberDetails); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(request.getStoreId())) { - throw new MenuCreationUnauthorizedException(); - } + validateViewAuthorization(user, request.getStoreId()); + // 메뉴 생성 로직 Menu toSave = request.toEntity(); Menu saved = menuRepository.save(toSave); @@ -58,10 +61,10 @@ public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails me @Transactional(readOnly = true) public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberDetails) { // 사용자 정보 가져오기 - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - + User user = getUser(memberDetails); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 - validateMenuViewAuthorization(user, storeId); + validateViewAuthorization(user, storeId); + List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() @@ -83,11 +86,10 @@ public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDe throw new MenuParamEmptyException(); } // 사용자 정보 가져오기 - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) - .orElseThrow(MenuNotFoundException::new); + User user = getUser(memberDetails); + Menu menu = getMenu(menuId); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 - validateMenuViewAuthorization(user, menu.getStoreId()); + validateViewAuthorization(user, menu.getStoreId()); List images = menuImageRepository.findByMenu(menu); List imageDto = images.stream() @@ -100,13 +102,10 @@ public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDe @Transactional public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findByIdAndDeletedFalse(menuId) - .orElseThrow(MenuNotFoundException::new); + User user = getUser(memberDetails); + Menu menu = getMenu(menuId); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) { - throw new MenuUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, menu.getStoreId()); menu.updateInfo( request.getAdminDisplayName(), @@ -138,11 +137,11 @@ public String updateMenuSortOrder(List requests, MemberDe } if (requests.stream().map(MenuSortUpdateRequest::getMenuId).distinct().count() != requests.size()) { - throw new IllegalArgumentException("중복된 메뉴 ID가 포함되어 있습니다."); + throw new MenuDuplicateIdException(); } if (requests.stream().anyMatch(r -> r.getSortOrder() == null || r.getSortOrder() < 0)) { - throw new IllegalArgumentException("잘못된 정렬 순서가 포함되어 있습니다. 정렬 순서는 0 이상의 정수여야 합니다."); + throw new MenuInvalidSortOrderException(); } List ids = requests.stream().map(MenuSortUpdateRequest::getMenuId).toList(); @@ -153,7 +152,7 @@ public String updateMenuSortOrder(List requests, MemberDe Long storeId = menus.get(0).getStoreId(); if (!menus.stream().allMatch(m -> storeId.equals(m.getStoreId()))) { - throw new IllegalArgumentException("다른 매장의 메뉴가 있습니다."); + throw new MenuCrossStoreConflictException(); } Map idToSort = requests.stream() @@ -169,12 +168,10 @@ public String updateMenuSortOrder(List requests, MemberDe @Transactional public String deleteMenu(Long menuId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findById(menuId).orElseThrow(MenuNotFoundException::new); + User user = getUser(memberDetails); + Menu menu = getMenu(menuId); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) { - throw new MenuDeleteUnauthorizedException(); - } + validateDeleteAuthorization(user, menu.getStoreId()); menu.markAsDeleted(); menuRepository.save(menu); @@ -191,10 +188,34 @@ public Boolean toggleSoldOut(Long menuId) { return menu.getIsSoldOut(); } - private static void validateMenuViewAuthorization(User user, Long storeId) { - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { throw new MenuViewUnauthorizedException(); } } + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new MenuUpdateUnauthorizedException(); + } + } + + private void validateDeleteAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new MenuDeleteUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + } + + private Menu getMenu(Long menuId) { + return menuRepository.findByIdAndDeletedFalse(menuId) + .orElseThrow(MenuNotFoundException::new); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index 0892bd08..ea6b2dab 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -139,14 +139,14 @@ public Boolean toggleActive(Long storeId) { private void validateViewAuthorization(User user, Long storeId) { if (!(Role.SUPER_ADMIN.equals(user.getRole()) || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { - throw new OrderViewUnauthorizedException(); + throw new StoreViewUnauthorizedException(); } } private void validateUpdateAuthorization(User user, Long storeId) { if (!(Role.SUPER_ADMIN.equals(user.getRole()) || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { - throw new OrderUpdateUnauthorizedException(); + throw new StoreUpdateUnauthorizedException(); } } diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 1f61d392..494df3d0 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -55,6 +55,11 @@ public enum ErrorMessage { MENU_VIEW_UNAUTHORIZED("메뉴 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu004"), MENU_UPDATE_UNAUTHORIZED("메뉴 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu005"), MENU_DELETE_UNAUTHORIZED("메뉴 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu006"), + MENU_INVALID_SORT_ORDER("잘못된 정렬 순서가 포함되어 있습니다. 정렬 순서는 0 이상의 정수여야 합니다.", "menu007"), + MENU_DUPLICATE_ID("중복된 메뉴 ID가 포함되어 있습니다.", "menu008"), + MENU_CROSS_STORE_CONFLICT("서로 다른 매장의 메뉴가 포함되어 있습니다.", "menu009"), + MENU_ALREADY_DELETED("이미 삭제된 메뉴입니다.", "menu010"), + MENU_TOGGLE_UNAUTHORIZED("메뉴 품절 상태 변경 권한이 없습니다.", "menu011"), // store STORE_PARAMETER_EMPTY("주점 생성 시 파라미터 정보가 없습니다.", "store001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java new file mode 100644 index 00000000..0a2202a3 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuAlreadyDeletedException extends RuntimeException { + public MenuAlreadyDeletedException() { + super(ErrorMessage.MENU_ALREADY_DELETED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java new file mode 100644 index 00000000..70caeae1 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuCrossStoreConflictException extends RuntimeException { + public MenuCrossStoreConflictException() { + super(ErrorMessage.MENU_CROSS_STORE_CONFLICT.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java new file mode 100644 index 00000000..761595ac --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuDuplicateIdException extends RuntimeException { + public MenuDuplicateIdException() { + super(ErrorMessage.MENU_DUPLICATE_ID.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java new file mode 100644 index 00000000..55570c3a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuInvalidSortOrderException extends RuntimeException { + public MenuInvalidSortOrderException() { + super(ErrorMessage.MENU_INVALID_SORT_ORDER.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java new file mode 100644 index 00000000..9dea0004 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuToggleUnauthorizedException extends RuntimeException { + public MenuToggleUnauthorizedException() { + super(ErrorMessage.MENU_TOGGLE_UNAUTHORIZED.getMessage()); + } +} From 814cbce0039bfca4e9deecb3a310ff6d376f017a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 19:28:38 +0900 Subject: [PATCH 568/685] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=EC=88=98?= =?UTF-8?q?=EB=8B=A8=20=EA=B4=80=EB=A0=A8=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StorePaymentServiceImpl.java | 48 ++++++++++++++----- ...orePaymentDeleteUnauthorizedException.java | 9 ++++ 2 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index a7852824..95aab0e3 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -13,6 +13,7 @@ import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentCreationUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentDeleteUnauthorizedException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentUpdateUnauthorizedException; @@ -37,14 +38,13 @@ public class StorePaymentServiceImpl implements StorePaymentService { public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); if (storePaymentRepository.findByStoreId(storeId).isPresent()) { throw new StorePaymentAlreadyExistsException(); } - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentCreationUnauthorizedException(); - } + validateCreationAuthorization(user, storeId); + StorePayment toSave = request.toEntity(storeId); StorePayment saved = storePaymentRepository.save(toSave); @@ -56,11 +56,9 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r public Optional getStorePaymentByStoreId(MemberDetails memberDetails) { if (memberDetails == null) throw new StorePaymentParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentViewUnauthorizedException(); - } + validateViewAuthorization(user, storeId); return storePaymentRepository.findByStoreId(storeId) .map(StorePaymentReadDto::fromEntity); @@ -71,11 +69,9 @@ public Optional getStorePaymentByStoreId(MemberDetails memb public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, storeId); StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) .orElseThrow(StorePaymentNotFoundException::new); @@ -89,4 +85,32 @@ public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, return StorePaymentReadDto.fromEntity(storePayment); } + + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + } + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentViewUnauthorizedException(); + } + } + + private void validateCreationAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentCreationUnauthorizedException(); + } + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentUpdateUnauthorizedException(); + } + } + + private void validateDeleteAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentDeleteUnauthorizedException(); + } + } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java new file mode 100644 index 00000000..c65f5e55 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storepayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentDeleteUnauthorizedException extends RuntimeException{ + public StorePaymentDeleteUnauthorizedException() { + super(ErrorMessage.STORE_PAYMENT_DELETE_UNAUTHORIZED.getMessage()); + } +} From 4378a71dd352a3ce5f51ffc23f5873052ea1425a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 19:33:25 +0900 Subject: [PATCH 569/685] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 14 ++++++++++++++ .../controller/StorePaymentController.java | 1 + .../com/nowait/common/exception/ErrorMessage.java | 5 ++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 1c08cc73..730d9ab9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -562,6 +562,20 @@ public ErrorResponse menuCounterUpdateException(MenuCounterUpdateException e, We return new ErrorResponse(e.getMessage(), MENU_COUNTER_UPDATE.getCode()); } + /** + * * 그 외 예외처리 + */ + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + public ErrorResponse handleUnexpectedException(Exception e, WebRequest request) { + alarm(e, request); + log.error("handleUnexpectedException", e); + return new ErrorResponse( + ErrorMessage.UNEXPECTED_ERROR.getMessage(), + ErrorMessage.UNEXPECTED_ERROR.getCode() + ); + } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 5bcf314d..203aea70 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -5,6 +5,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 494df3d0..754b4ba6 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -87,7 +87,10 @@ public enum ErrorMessage { IMAGE_FILE_NOT_FOUND("DB에 해당 이미지 메타데이터가 존재하지 않습니다.", "image002"), // search - SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"); + SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"), + + // common + UNEXPECTED_ERROR("예상하지 못한 오류가 발생했습니다.", "common999"); private final String message; private final String code; From b8c90a9f43a28e8143e2813c83e6af88962a52bd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 19 Sep 2025 19:57:38 +0900 Subject: [PATCH 570/685] =?UTF-8?q?feat:=20NPE=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationadmin/order/service/OrderService.java | 3 +++ .../reservation/service/ReservationService.java | 6 +++++- .../applicationadmin/store/service/StoreServiceImpl.java | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index aaa34365..0ef84e73 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -147,6 +147,9 @@ private void validateUpdateAuthorization(User user, Long storeId) { } private User getUser(MemberDetails memberDetails) { + if (memberDetails == null) { + throw new OrderViewUnauthorizedException(); + } return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 4d728c14..6860d13f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -29,6 +29,7 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; +import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; import com.nowait.domaincoreredis.reservation.exception.ReservationDataInconsistencyException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; @@ -233,7 +234,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me } if (reservationNumber == null || currStatus == null) { - new ReservationDataInconsistencyException( + throw new ReservationDataInconsistencyException( String.format("storeId=%d, userId=%s, reservationNumber=%s, status=%s, partySize=%s", storeId, userId, reservationNumber, currStatus, partySize) ); @@ -540,6 +541,9 @@ private void validateUpdateAuthorization(User user, Long storeId) { } private User getUser(MemberDetails memberDetails) { + if (memberDetails == null) { + throw new ReservationViewUnauthorizedException(); + } return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index ea6b2dab..da356da0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -151,6 +151,9 @@ private void validateUpdateAuthorization(User user, Long storeId) { } private User getUser(MemberDetails memberDetails) { + if (memberDetails == null) { + throw new StoreViewUnauthorizedException(); + } return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } From ee96177d1201acc0047f2389cf1a6b181c931b2e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 20 Sep 2025 00:47:11 +0900 Subject: [PATCH 571/685] =?UTF-8?q?feat:=20=EC=A0=84=EC=97=AD=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index 730d9ab9..ad81fbf7 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -72,6 +72,7 @@ import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincoreredis.rank.exception.MenuCounterUpdateException; +import com.nowait.domaincoreredis.reservation.exception.ReservationDataInconsistencyException; import io.swagger.v3.oas.annotations.Hidden; import lombok.RequiredArgsConstructor; @@ -562,6 +563,14 @@ public ErrorResponse menuCounterUpdateException(MenuCounterUpdateException e, We return new ErrorResponse(e.getMessage(), MENU_COUNTER_UPDATE.getCode()); } + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(ReservationDataInconsistencyException.class) + public ErrorResponse reservationDataInconsistencyException(ReservationDataInconsistencyException e, WebRequest request) { + alarm(e, request); + log.error("reservationDataInconsistencyException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_DATA_INCONSISTENCY.getCode()); + } + /** * * 그 외 예외처리 */ From 5bac8f21ae390e4fcd5254e904289128e7ffa479 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 20 Sep 2025 00:52:26 +0900 Subject: [PATCH 572/685] =?UTF-8?q?refactor:=20redis=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=82=AD=EC=A0=9C=20=EB=88=84=EB=9D=BD=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingRedisRepository.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index f9eb921b..1af5b528 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -82,9 +82,7 @@ public void deleteWaiting(Long storeId, String userId) { redisTemplate.opsForHash().delete(numberMapKey, userId); // reservationNumber → userId 삭제 - if (reservationNumber != null) { - redisTemplate.opsForHash().delete(userMapKey, reservationNumber); - } + redisTemplate.opsForHash().delete(userMapKey, reservationNumber); String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForHash().delete(statusKey, userId); From d6ea54ec07c140980ef17c52135f35a939dbea2e Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 20 Sep 2025 01:03:01 +0900 Subject: [PATCH 573/685] =?UTF-8?q?refactor:=20Reservation=20status=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 6860d13f..b2a0040a 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -286,6 +286,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .partySize(partySize) .requestedAt(requestedAt) .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .status(ReservationStatus.valueOf(currStatus)) .build(); // 호출 시각 반영 @@ -344,6 +345,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me .partySize(partySize) .requestedAt(requestedAt) .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .status(ReservationStatus.valueOf(currStatus)) .build(); r.markUpdated(LocalDateTime.now(), ReservationStatus.CANCELLED); From c54b05a247eb234ca7e09dc6fc17ba1797122ff6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 7 Nov 2025 18:14:06 +0900 Subject: [PATCH 574/685] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/build.gradle | 2 +- .../applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 3a65d7ac..0f5b4c6e 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' } -jar { +jar { enabled = false } bootJar { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index c6e2dcf7..13dc0ecd 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -71,7 +71,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) - String targetUrl = "https://nowait-user.vercel.app/login/success?accessToken=" + accessToken; + String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + accessToken; response.sendRedirect(targetUrl); } } From e4941a5e2e0539399dd278a6c5d6d28dfa06ecb7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 7 Nov 2025 18:24:24 +0900 Subject: [PATCH 575/685] =?UTF-8?q?refactor:=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B2=84=EC=A0=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-admin-api/Dockerfile | 2 +- nowait-app-user-api/Dockerfile | 2 +- nowait-common/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nowait-app-admin-api/Dockerfile b/nowait-app-admin-api/Dockerfile index c105544d..a3f3e994 100644 --- a/nowait-app-admin-api/Dockerfile +++ b/nowait-app-admin-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:21-slim +FROM eclipse-temurin:21 ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-admin-api.jar diff --git a/nowait-app-user-api/Dockerfile b/nowait-app-user-api/Dockerfile index 785def27..2fee4610 100644 --- a/nowait-app-user-api/Dockerfile +++ b/nowait-app-user-api/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:21-slim +FROM eclipse-temurin:21 ARG JAR_FILE=./build/libs/*.jar COPY ${JAR_FILE} nowait-app-user-api.jar diff --git a/nowait-common/Dockerfile b/nowait-common/Dockerfile index ce3e2380..da287cab 100644 --- a/nowait-common/Dockerfile +++ b/nowait-common/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:21-slim +FROM eclipse-temurin:21 WORKDIR /app COPY build/libs/*.jar nowait-common.jar From 71dd2ee5e75d469bc5b545915f2505052258d071 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 7 Nov 2025 18:39:18 +0900 Subject: [PATCH 576/685] =?UTF-8?q?refactor:=20cors=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index cf844f46..4a9baba3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -15,7 +15,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 쿠키나 인증헤더 자격증명 허용 - config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342", "https://nowait-user.vercel.app", "https://nowait.co.kr")); // 허용할 출처 설정 + config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:63342", "https://nowait.co.kr", "https://app.nowait.co.kr")); // 허용할 출처 설정 config.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")); // 메서드 허용 config.setAllowedHeaders(List.of("*")); //클라이언트가 보낼 수 있는 헤더 config.setExposedHeaders(List.of("Authorization")); //클라이언트(브라우저)가 접근할 수 있는 헤더 지정 From fcbc0028d3d261a3f4bee83cf9c96b36630b4460 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 7 Nov 2025 18:51:13 +0900 Subject: [PATCH 577/685] =?UTF-8?q?refactor:=20csrf=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/config/security/SecurityConfig.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 17ab83f4..db6f7a69 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -37,12 +37,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.configurationSource(corsConfigurationSource)) // CSRF 방어 기능 비활성화 (jwt 토큰을 사용할 것이기에 필요없음) - // .csrf(csrf -> csrf - // .ignoringRequestMatchers( - // "/api/**", "/login/**", "/oauth2/**", - // "/swagger-ui/**", "/v3/api-docs/**", "/orders/**") - // .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - // ) .csrf(AbstractHttpConfigurer::disable) // 시큐리티 폼 로그인 비활성화 .formLogin(AbstractHttpConfigurer::disable) From 2d80e612e3506fae527fe6c5f2e2c39f4fcc82bd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 9 Nov 2025 20:01:28 +0900 Subject: [PATCH 578/685] =?UTF-8?q?refactor:=20sameSite=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index 4a9baba3..e81dc60d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -25,3 +25,4 @@ public CorsConfigurationSource corsConfigurationSource() { return source; } } + From 6a91ba659622667738e0d6e00f7387834afedce6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 9 Nov 2025 20:01:41 +0900 Subject: [PATCH 579/685] =?UTF-8?q?bug:=20sameSite=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationuser/config/security/CorsConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java index e81dc60d..b0c5b9b6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/CorsConfig.java @@ -26,3 +26,4 @@ public CorsConfigurationSource corsConfigurationSource() { } } + From a5b7f8547775616b3ae8346da1141176fcbfadba Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 9 Nov 2025 20:35:00 +0900 Subject: [PATCH 580/685] =?UTF-8?q?bug:=20sameSite=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 13dc0ecd..260b1b8f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -64,7 +64,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .secure(false) // 운영환경에서는 true .path("/") .maxAge(30L * 24 * 60 * 60) // 30일 (초 단위) - .sameSite("Lax") + .sameSite("Strict") .build(); // 기존 방식 대신 ResponseCookie.toString()을 헤더로 추가 From bba944f29f584e5d3e08271a30cfd3f581f2d31a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 9 Nov 2025 20:56:31 +0900 Subject: [PATCH 581/685] =?UTF-8?q?bug:=20sameSite=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 260b1b8f..13dc0ecd 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -64,7 +64,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .secure(false) // 운영환경에서는 true .path("/") .maxAge(30L * 24 * 60 * 60) // 30일 (초 단위) - .sameSite("Strict") + .sameSite("Lax") .build(); // 기존 방식 대신 ResponseCookie.toString()을 헤더로 추가 From dde42a8d181acbf784423aba2083da2a294c1e05 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Wed, 12 Nov 2025 15:54:15 +0900 Subject: [PATCH 582/685] =?UTF-8?q?fix(user,admin):=20n+1=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20=EB=B0=8F=20getUser=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 주문 조회 시 n+1 문제 해결(@EntityGraph) - getuser 로직으로 인한 user 2회 중복 조회 로직 1회 수정으로 변 --- nowait-app-admin-api/build.gradle | 33 ++++++++------ .../menu/service/MenuService.java | 16 +++---- .../order/service/OrderService.java | 32 +++++++++----- .../service/ReservationService.java | 22 +++++----- .../store/service/StoreServiceImpl.java | 18 ++++---- .../controller/StorePaymentController.java | 3 +- .../service/StorePaymentService.java | 2 +- .../service/StorePaymentServiceImpl.java | 14 +++--- .../user/dto/ManagerSignupRequestDto.java | 8 ++++ nowait-app-user-api/build.gradle | 44 ++++++++----------- .../order/repository/OrderRepository.java | 1 + .../user/entity/MemberDetails.java | 6 +++ 12 files changed, 111 insertions(+), 88 deletions(-) diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 0f5b4c6e..970728dc 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -12,6 +12,7 @@ bootJar { group = 'com.nowait' version = '0.0.1-SNAPSHOT' + repositories { mavenCentral() maven { url 'https://jitpack.io' } @@ -29,11 +30,11 @@ dependencyManagement { dependencies { implementation project(':nowait-common') - implementation project(':nowait-infra') + implementation project(':nowait-infra') // 사용자 관련 도메인 implementation project(':nowait-domain:domain-admin-rdb') implementation project(':nowait-domain:domain-core-rdb') implementation project(':nowait-domain:domain-redis') - implementation project(':nowait-event') + implementation project(":nowait-event") // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' @@ -42,23 +43,27 @@ dependencies { // SPRING SECURITY implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // jwt implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' - // redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + // OAUTH2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' // DB implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // 디스코드 웹훅 implementation 'com.github.napstr:logback-discord-appender:1.0.0' @@ -78,12 +83,14 @@ dependencies { // test testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation platform("org.testcontainers:testcontainers-bom:1.20.1") - testImplementation "org.testcontainers:junit-jupiter" - testImplementation "org.testcontainers:testcontainers" - + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'com.redis:testcontainers-redis:2.2.4' } test { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 50805e1c..a877a5fb 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -47,7 +47,7 @@ public class MenuService { @Transactional public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) { // 사용자 정보 가져오기 - User user = getUser(memberDetails); + User user = memberDetails.getUser(); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 validateViewAuthorization(user, request.getStoreId()); @@ -61,7 +61,7 @@ public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails me @Transactional(readOnly = true) public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberDetails) { // 사용자 정보 가져오기 - User user = getUser(memberDetails); + User user = memberDetails.getUser(); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 validateViewAuthorization(user, storeId); @@ -86,7 +86,7 @@ public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDe throw new MenuParamEmptyException(); } // 사용자 정보 가져오기 - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Menu menu = getMenu(menuId); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 validateViewAuthorization(user, menu.getStoreId()); @@ -102,7 +102,7 @@ public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDe @Transactional public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Menu menu = getMenu(menuId); validateUpdateAuthorization(user, menu.getStoreId()); @@ -168,7 +168,7 @@ public String updateMenuSortOrder(List requests, MemberDe @Transactional public String deleteMenu(Long menuId, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Menu menu = getMenu(menuId); validateDeleteAuthorization(user, menu.getStoreId()); @@ -210,9 +210,9 @@ private void validateDeleteAuthorization(User user, Long storeId) { } } - private User getUser(MemberDetails memberDetails) { - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - } + // private User getUser(MemberDetails memberDetails) { + // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + // } private Menu getMenu(Long menuId) { return menuRepository.findByIdAndDeletedFalse(menuId) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 0ef84e73..5ba88721 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -34,9 +34,11 @@ import com.nowait.nowaitevent.order.event.OrderCancelledEvent; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Service @RequiredArgsConstructor +@Slf4j public class OrderService { private final OrderRepository orderRepository; private final StatisticCustomRepository statisticCustomRepository; @@ -46,24 +48,29 @@ public class OrderService { @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { - User user = getUser(memberDetails); + log.info("getUser 호출 전"); + User user = memberDetails.getUser(); + log.info("getUser 호출 완료"); storeRepository.findByStoreIdAndDeletedFalse(storeId).orElseThrow(StoreNotFoundException::new); + log.info("Store 조회 완료"); validateViewAuthorization(user, storeId); LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); LocalDateTime startDateTime = today.atStartOfDay(); LocalDateTime endDateTime = today.plusDays(1).atStartOfDay(); - return orderRepository.findAllByStore_StoreIdAndCreatedAtBetween(storeId, startDateTime, endDateTime) + List order = orderRepository.findAllByStore_StoreIdAndCreatedAtBetween(storeId, startDateTime, endDateTime) .stream() .map(OrderResponseDto::fromEntity) .collect(Collectors.toList()); + log.info("Order 조회 완료"); + return order; } @Transactional public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); Long storeId = userOrder.getStore().getStoreId(); @@ -92,7 +99,7 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus @Transactional public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest cancelOrderRequest, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); validateUpdateAuthorization(user, userOrder.getStore().getStoreId()); @@ -113,7 +120,7 @@ public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest @Transactional(readOnly = true) public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails, LocalDate date) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Long storeId = user.getStoreId(); validateViewAuthorization(user, storeId); @@ -124,7 +131,7 @@ public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails, Loca // 현재는 사용하지 않음. 향후 관리자 통계 페이지 확장 시 활용 가능 @Transactional(readOnly = true) public List getTop5StoresBySalesToday(MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Long storeId = user.getStoreId(); validateUpdateAuthorization(user, storeId); @@ -146,10 +153,11 @@ private void validateUpdateAuthorization(User user, Long storeId) { } } - private User getUser(MemberDetails memberDetails) { - if (memberDetails == null) { - throw new OrderViewUnauthorizedException(); - } - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - } + // private User getUser(MemberDetails memberDetails) { + // if (memberDetails == null) { + // throw new OrderViewUnauthorizedException(); + // } + // // findById로 Select 쿼리 나가는 것을 getReferenceById로 변경하여 방지 + // return userRepository.getReferenceById(memberDetails.getId()); + // } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index b2a0040a..42a54d59 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -61,7 +61,7 @@ public class ReservationService { //TODO 성능 비교를 위해 남겨둔 로직 @Transactional(readOnly = true) public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); validateViewAuthorization(user, storeId); List reservations = reservationRepository.findAllByStore_StoreIdOrderByRequestedAtAsc(storeId); @@ -96,7 +96,7 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem @Transactional public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Reservation reservation = reservationRepository.findById(reservationId) .orElseThrow(ReservationNotFoundException::new); validateUpdateAuthorization(user, reservation.getStore().getStoreId()); @@ -195,7 +195,7 @@ public List getAllWaitingUserDetails(Long storeId) { // 완료 or 취소 처리된 대기 리스트 조회 @Transactional(readOnly = true) public List getCompletedWaitingUserDetails(Long storeId, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = memberDetails.getUser(); validateViewAuthorization(user, storeId); List reservations = findTodayWaiting(storeId); @@ -214,7 +214,7 @@ public List getCompletedWaitingUserDetails(Long storeId, Me public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, ReservationStatus newStatus) { - User user = getUser(member); + User user = member.getUser(); validateUpdateAuthorization(user, storeId); if (userId == null || userId.isBlank()) { @@ -364,7 +364,7 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me public EntryStatusResponseDto processEntryStatusByReservationNumber(Long storeId, String reservationNumber, MemberDetails member, ReservationStatus newStatus) { - User user = getUser(member); + User user = member.getUser(); validateUpdateAuthorization(user, storeId); if (reservationNumber == null || reservationNumber.isBlank()) { @@ -542,11 +542,11 @@ private void validateUpdateAuthorization(User user, Long storeId) { } } - private User getUser(MemberDetails memberDetails) { - if (memberDetails == null) { - throw new ReservationViewUnauthorizedException(); - } - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - } + // private User getUser(MemberDetails memberDetails) { + // if (memberDetails == null) { + // throw new ReservationViewUnauthorizedException(); + // } + // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + // } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index da356da0..55354ed6 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -59,7 +59,7 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { @Transactional(readOnly = true) public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails memberDetails) { if (storeId == null) throw new StoreParamEmptyException(); - User user = getUser(memberDetails); + User user = memberDetails.getUser(); validateViewAuthorization(user, storeId); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) @@ -82,7 +82,7 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails mem public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, MemberDetails memberDetails) { if (storeId == null || request == null) throw new StoreParamEmptyException(); - User user = getUser(memberDetails); + User user = memberDetails.getUser(); validateUpdateAuthorization(user, storeId); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) @@ -115,7 +115,7 @@ public String deleteStore(Long storeId, MemberDetails memberDetails) { throw new StoreParamEmptyException(); } - User user = getUser(memberDetails); + User user = memberDetails.getUser(); validateUpdateAuthorization(user, storeId); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) @@ -150,10 +150,10 @@ private void validateUpdateAuthorization(User user, Long storeId) { } } - private User getUser(MemberDetails memberDetails) { - if (memberDetails == null) { - throw new StoreViewUnauthorizedException(); - } - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - } + // private User getUser(MemberDetails memberDetails) { + // if (memberDetails == null) { + // throw new StoreViewUnauthorizedException(); + // } + // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + // } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 203aea70..d8325258 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -5,7 +5,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -13,11 +12,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationadmin.storePayment.service.StorePaymentService; import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; import com.nowait.applicationadmin.storepayment.dto.StorePaymentReadDto; import com.nowait.applicationadmin.storepayment.dto.StorePaymentUpdateRequest; -import com.nowait.applicationadmin.storepayment.service.StorePaymentService; import com.nowait.common.api.ApiUtils; import com.nowait.domaincorerdb.user.entity.MemberDetails; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java index 7dad2fe2..3429ba71 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentService.java @@ -1,4 +1,4 @@ -package com.nowait.applicationadmin.storepayment.service; +package com.nowait.applicationadmin.storePayment.service; import java.util.Optional; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index 95aab0e3..91e80374 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationadmin.storePayment.service.StorePaymentService; import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateRequest; import com.nowait.applicationadmin.storepayment.dto.StorePaymentCreateResponse; import com.nowait.applicationadmin.storepayment.dto.StorePaymentReadDto; @@ -21,7 +22,6 @@ import com.nowait.domaincorerdb.storepayment.repository.StorePaymentRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; -import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -38,7 +38,7 @@ public class StorePaymentServiceImpl implements StorePaymentService { public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Long storeId = user.getStoreId(); if (storePaymentRepository.findByStoreId(storeId).isPresent()) { throw new StorePaymentAlreadyExistsException(); @@ -56,7 +56,7 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r public Optional getStorePaymentByStoreId(MemberDetails memberDetails) { if (memberDetails == null) throw new StorePaymentParamEmptyException(); - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Long storeId = user.getStoreId(); validateViewAuthorization(user, storeId); @@ -69,7 +69,7 @@ public Optional getStorePaymentByStoreId(MemberDetails memb public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); - User user = getUser(memberDetails); + User user = memberDetails.getUser(); Long storeId = user.getStoreId(); validateUpdateAuthorization(user, storeId); StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) @@ -86,9 +86,9 @@ public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, return StorePaymentReadDto.fromEntity(storePayment); } - private User getUser(MemberDetails memberDetails) { - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - } + // private User getUser(MemberDetails memberDetails) { + // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + // } private void validateViewAuthorization(User user, Long storeId) { if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java index 0dd4fbcd..7cadbf2f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/dto/ManagerSignupRequestDto.java @@ -36,15 +36,23 @@ public class ManagerSignupRequestDto { @Schema(description = "로그인타입", example = "LOCAL") private String socialType; + @Schema(description = "마케팅 수신 동의", example = "true") + private boolean isMarketingAgree; + + @Schema(description = "폰 번호 입력 여부", example = "true") + private boolean phoneEntered; public User toEntity() { return User.builder() + .profileImage("no") .email(email) .phoneNumber("") .password(password) .nickname(nickname) .socialType(SocialType.LOCAL) .role(Role.MANAGER) + .isMarketingAgree(isMarketingAgree) + .phoneEntered(false) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index ab3af0b2..deb88a41 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -12,7 +12,6 @@ bootJar { group = 'com.nowait' version = '0.0.1-SNAPSHOT' - repositories { mavenCentral() maven { url 'https://jitpack.io' } @@ -29,11 +28,12 @@ dependencyManagement { } dependencies { - implementation project(':nowait-common') - implementation project(':nowait-infra') // 사용자 관련 도메인 - implementation project(':nowait-domain:domain-user-rdb') - implementation project(':nowait-domain:domain-core-rdb') - implementation project(':nowait-domain:domain-redis') + implementation project(":nowait-common") + implementation project(":nowait-domain:domain-core-rdb") + implementation project(":nowait-domain:domain-user-rdb") // ✅ 이 줄 꼭 있어야 합니다 + implementation project(":nowait-domain:domain-redis") + implementation project(":nowait-event") + implementation project(":nowait-infra") // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' @@ -42,26 +42,22 @@ dependencies { // SPRING SECURITY implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // jwt implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' - // OAUTH2 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' - implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' - - // DB - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'com.mysql:mysql-connector-j' + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' // SWAGGER implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' - // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // DB + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'com.mysql:mysql-connector-j' // 디스코드 웹훅 implementation 'com.github.napstr:logback-discord-appender:1.0.0' @@ -75,21 +71,19 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' // actuator - implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-actuator' - // prometheus + // prometheus implementation 'io.micrometer:micrometer-registry-prometheus' // test testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.junit.jupiter:junit-jupiter-api' - testImplementation 'org.junit.jupiter:junit-jupiter-engine' - testImplementation 'org.mockito:mockito-core' - testImplementation 'org.mockito:mockito-junit-jupiter' - testImplementation 'org.testcontainers:junit-jupiter' - testImplementation 'com.redis:testcontainers-redis:2.2.4' + testImplementation platform("org.testcontainers:testcontainers-bom:1.20.1") + testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:testcontainers" + } test { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java index d5bb6bf4..05da4d75 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java @@ -13,6 +13,7 @@ public interface OrderRepository extends JpaRepository { boolean existsBySignatureAndCreatedAtAfter(String signature, LocalDateTime createdAt); + @EntityGraph(attributePaths = {"store", "orderItems", "orderItems.menu"}) List findByStore_PublicCodeAndTableIdAndSessionId(String publicCode, Long tableId, String sessionId); @EntityGraph(attributePaths = {"orderItems", "orderItems.menu"}) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java index f9bc7b3f..9a50fabd 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/MemberDetails.java @@ -16,6 +16,7 @@ public class MemberDetails implements UserDetails { private final String email; private final String password; private final Collection authorities; + private final User user; public static MemberDetails create(User user) { return MemberDetails.builder() @@ -23,9 +24,14 @@ public static MemberDetails create(User user) { .email(user.getEmail()) .password(user.getPassword()) .authorities(List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))) + .user(user) .build(); } + public User getUser() { + return user; + } + public Long getId() { return id; } @Override From 8e7947773a3dc873c6f208ad6a643d02d7bafad2 Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Wed, 12 Nov 2025 16:11:35 +0900 Subject: [PATCH 583/685] =?UTF-8?q?build.gradle=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-user-api/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index deb88a41..a8cafc73 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -30,9 +30,8 @@ dependencyManagement { dependencies { implementation project(":nowait-common") implementation project(":nowait-domain:domain-core-rdb") - implementation project(":nowait-domain:domain-user-rdb") // ✅ 이 줄 꼭 있어야 합니다 + implementation project(":nowait-domain:domain-user-rdb") implementation project(":nowait-domain:domain-redis") - implementation project(":nowait-event") implementation project(":nowait-infra") // Spring Boot Starter From 1baf365d54738f717e6fd28d0674eac6e5a4189c Mon Sep 17 00:00:00 2001 From: jeonghyemin Date: Wed, 12 Nov 2025 16:18:45 +0900 Subject: [PATCH 584/685] =?UTF-8?q?refactor(admin):=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getUser 주석 및 로그 삭제 --- nowait-app-admin-api/build.gradle | 2 +- .../applicationadmin/menu/service/MenuService.java | 4 ---- .../applicationadmin/order/service/OrderService.java | 12 ------------ .../reservation/service/ReservationService.java | 7 ------- .../store/service/StoreServiceImpl.java | 7 ------- .../service/StorePaymentServiceImpl.java | 4 ---- 6 files changed, 1 insertion(+), 35 deletions(-) diff --git a/nowait-app-admin-api/build.gradle b/nowait-app-admin-api/build.gradle index 970728dc..2dee33e3 100644 --- a/nowait-app-admin-api/build.gradle +++ b/nowait-app-admin-api/build.gradle @@ -34,7 +34,7 @@ dependencies { implementation project(':nowait-domain:domain-admin-rdb') implementation project(':nowait-domain:domain-core-rdb') implementation project(':nowait-domain:domain-redis') - implementation project(":nowait-event") + implementation project(':nowait-event') // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index a877a5fb..c362912d 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -210,10 +210,6 @@ private void validateDeleteAuthorization(User user, Long storeId) { } } - // private User getUser(MemberDetails memberDetails) { - // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - // } - private Menu getMenu(Long menuId) { return menuRepository.findByIdAndDeletedFalse(menuId) .orElseThrow(MenuNotFoundException::new); diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index 5ba88721..d4b500f2 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -48,11 +48,8 @@ public class OrderService { @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { - log.info("getUser 호출 전"); User user = memberDetails.getUser(); - log.info("getUser 호출 완료"); storeRepository.findByStoreIdAndDeletedFalse(storeId).orElseThrow(StoreNotFoundException::new); - log.info("Store 조회 완료"); validateViewAuthorization(user, storeId); @@ -63,7 +60,6 @@ public List findAllOrders(Long storeId, MemberDetails memberDe .stream() .map(OrderResponseDto::fromEntity) .collect(Collectors.toList()); - log.info("Order 조회 완료"); return order; } @@ -152,12 +148,4 @@ private void validateUpdateAuthorization(User user, Long storeId) { throw new OrderUpdateUnauthorizedException(); } } - - // private User getUser(MemberDetails memberDetails) { - // if (memberDetails == null) { - // throw new OrderViewUnauthorizedException(); - // } - // // findById로 Select 쿼리 나가는 것을 getReferenceById로 변경하여 방지 - // return userRepository.getReferenceById(memberDetails.getId()); - // } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index 42a54d59..a9a3e597 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -541,12 +541,5 @@ private void validateUpdateAuthorization(User user, Long storeId) { throw new ReservationUpdateUnauthorizedException(); } } - - // private User getUser(MemberDetails memberDetails) { - // if (memberDetails == null) { - // throw new ReservationViewUnauthorizedException(); - // } - // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - // } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index 55354ed6..acdf94d0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -149,11 +149,4 @@ private void validateUpdateAuthorization(User user, Long storeId) { throw new StoreUpdateUnauthorizedException(); } } - - // private User getUser(MemberDetails memberDetails) { - // if (memberDetails == null) { - // throw new StoreViewUnauthorizedException(); - // } - // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - // } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index 91e80374..8e723f10 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -86,10 +86,6 @@ public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, return StorePaymentReadDto.fromEntity(storePayment); } - // private User getUser(MemberDetails memberDetails) { - // return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - // } - private void validateViewAuthorization(User user, Long storeId) { if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { throw new StorePaymentViewUnauthorizedException(); From 54698db2e53f352a302d7076c8f350f4d09fbffc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:46:20 +0900 Subject: [PATCH 585/685] =?UTF-8?q?feat:=20=EC=95=B1=20=EC=A0=84=EC=9A=A9?= =?UTF-8?q?=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/config/security/SecurityConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index db6f7a69..8e717a86 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -14,8 +14,8 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; -import com.nowait.applicationuser.oauth.oauth2.CustomOAuth2UserService; -import com.nowait.applicationuser.oauth.oauth2.OAuth2LoginSuccessHandler; +import com.nowait.applicationuser.oauth.oauth2.web.CustomOAuth2UserService; +import com.nowait.applicationuser.oauth.oauth2.web.OAuth2LoginSuccessHandler; import com.nowait.applicationuser.security.jwt.JwtAuthorizationFilter; import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.security.service.CustomUserDetailService; @@ -58,6 +58,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/oauth2/authorization/kakao", // 카카오 로그인 요청 "/login/oauth2/code/**", // 카카오 인증 콜백 "/api/refresh-token", // refresh token (토큰 갱신) + "/v2/app/oauth/kakao/login", // 카카오 앱 로그인 "/v1/menus/**", // 모든 메뉴 조회 "/v1/store-payments/**", // 결제 관련 API "/orders/**", From a98541cf5b25bbc253804bce671b719803f6fad9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:46:54 +0900 Subject: [PATCH 586/685] =?UTF-8?q?refactor:=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/applicationadmin/menu/service/MenuService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 50805e1c..e92513ab 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -156,7 +157,7 @@ public String updateMenuSortOrder(List requests, MemberDe } Map idToSort = requests.stream() - .collect(java.util.stream.Collectors.toMap(MenuSortUpdateRequest::getMenuId, + .collect(Collectors.toMap(MenuSortUpdateRequest::getMenuId, MenuSortUpdateRequest::getSortOrder)); menus.forEach(m -> m.updateSortOrder(idToSort.get(m.getId()))); From 4dbea442afd0e7567bdeb67d0caa14a7298f8f5c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:47:36 +0900 Subject: [PATCH 587/685] =?UTF-8?q?refactor:=20=EC=9A=A9=EB=8F=84=EB=B3=84?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/oauth2/global/AuthTokenService.java | 69 +++++++++++++++++++ .../oauth/oauth2/global/OAuthUserService.java | 44 ++++++++++++ .../{ => web}/OAuth2LoginSuccessHandler.java | 32 ++------- 3 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/{ => web}/OAuth2LoginSuccessHandler.java (60%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java new file mode 100644 index 00000000..3dd9c9f5 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java @@ -0,0 +1,69 @@ +package com.nowait.applicationuser.oauth.oauth2.global; + +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.security.jwt.JwtUtil; +import com.nowait.domaincorerdb.token.entity.Token; +import com.nowait.domaincorerdb.token.repository.TokenRepository; +import com.nowait.domaincorerdb.user.entity.User; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AuthTokenService { + + private final JwtUtil jwtUtil; + private final TokenRepository tokenRepository; + + @Transactional + public TokenResult issueTokens(User user) { + Long userId = user.getId(); + String role = user.getRole().name(); + + String accessToken = jwtUtil.createAccessToken( + "accessToken", + userId, + role, + Boolean.TRUE.equals(user.getPhoneEntered()), + Boolean.TRUE.equals(user.getIsMarketingAgree()), + 60 * 60 * 2000L + ); // 2시간 + + String refreshToken = jwtUtil.createRefreshToken( + "refreshToken", + userId, + 30L * 24 * 60 * 60 * 1000L + ); // 30일 + + // refreshToken을 DB에 저장 or update + Optional tokenOptional = tokenRepository.findByUserId(user.getId()); + if (tokenOptional.isPresent()) { + Token token = tokenOptional.get(); + token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); + } else { + Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); + tokenRepository.save(token); + } + + return new TokenResult(accessToken, refreshToken); + } + + public static class TokenResult { + private final String accessToken; + private final String refreshToken; + + public TokenResult(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public String getAccessToken() { return accessToken; } + public String getRefreshToken() { return refreshToken; } + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java new file mode 100644 index 00000000..4fafea6b --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java @@ -0,0 +1,44 @@ +package com.nowait.applicationuser.oauth.oauth2.global; + +import java.time.LocalDateTime; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.oauth.dto.web.OAuth2Response; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OAuthUserService { + private final UserRepository userRepository; + + @Transactional + public User loadOrCreateUser(OAuth2Response oAuth2Response) { + return userRepository.findByEmail(oAuth2Response.getEmail()) + .orElseGet(() -> createUser(oAuth2Response)); + } + + private User createUser(OAuth2Response oAuth2Response) { + User user = User.builder() + .email(oAuth2Response.getEmail()) + .phoneNumber("") + .nickname(oAuth2Response.getNickName()) + .profileImage(oAuth2Response.getProfileImage()) + .socialType(SocialType.KAKAO) + .role(Role.USER) // 일반 유저 설정 + .storeId(0L) + .phoneEntered(false) + .isMarketingAgree(false) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + return userRepository.save(user); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/OAuth2LoginSuccessHandler.java similarity index 60% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/OAuth2LoginSuccessHandler.java index 13dc0ecd..584f29e6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/OAuth2LoginSuccessHandler.java @@ -1,8 +1,6 @@ -package com.nowait.applicationuser.oauth.oauth2; +package com.nowait.applicationuser.oauth.oauth2.web; import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Optional; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; @@ -10,9 +8,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationuser.security.jwt.JwtUtil; -import com.nowait.domaincorerdb.token.entity.Token; -import com.nowait.domaincorerdb.token.repository.TokenRepository; +import com.nowait.applicationuser.oauth.oauth2.global.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -30,8 +26,7 @@ @RequiredArgsConstructor @Slf4j public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final JwtUtil jwtUtil; - private final TokenRepository tokenRepository; + private final AuthTokenService authTokenService; @Override @Transactional @@ -40,26 +35,11 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); User user = customUserDetails.getUser(); - Long userId = customUserDetails.getUserId(); - String role = authentication.getAuthorities().iterator().next().getAuthority(); - // JWT 발급 - String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, - Boolean.TRUE.equals(user.getPhoneEntered()), Boolean.TRUE.equals(user.getIsMarketingAgree()),60 * 60 * 1000L); // 1시간 - String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, 30L * 24 * 60 * 60 * 1000L); // 30일 - - // 1. refreshToken을 DB에 저장 or update - Optional tokenOptional = tokenRepository.findByUserId(user.getId()); - if (tokenOptional.isPresent()) { - Token token = tokenOptional.get(); - token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); - } else { - Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(token); - } + AuthTokenService.TokenResult tokenResult = authTokenService.issueTokens(user); // 2. refreshToken을 HttpOnly 쿠키로 설정 (ResponseCookie로) - ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken) + ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", tokenResult.getRefreshToken()) .httpOnly(true) .secure(false) // 운영환경에서는 true .path("/") @@ -71,7 +51,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) - String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + accessToken; + String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + tokenResult.getAccessToken(); response.sendRedirect(targetUrl); } } From 5b0d548db27ee1b22d8a0569e02f409ccdfc2adc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:47:50 +0900 Subject: [PATCH 588/685] =?UTF-8?q?refactor:=20=EC=9A=A9=EB=8F=84=EB=B3=84?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => web}/CustomOAuth2UserService.java | 45 +++---------------- 1 file changed, 7 insertions(+), 38 deletions(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/{ => web}/CustomOAuth2UserService.java (53%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/CustomOAuth2UserService.java similarity index 53% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/CustomOAuth2UserService.java index 048dc43b..7459252a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/CustomOAuth2UserService.java @@ -1,7 +1,4 @@ -package com.nowait.applicationuser.oauth.oauth2; - -import java.time.LocalDateTime; -import java.util.Optional; +package com.nowait.applicationuser.oauth.oauth2.web; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -9,12 +6,10 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import com.nowait.applicationuser.oauth.dto.KaKaoResponse; -import com.nowait.applicationuser.oauth.dto.OAuth2Response; -import com.nowait.common.enums.Role; -import com.nowait.common.enums.SocialType; +import com.nowait.applicationuser.oauth.dto.web.KaKaoResponse; +import com.nowait.applicationuser.oauth.dto.web.OAuth2Response; +import com.nowait.applicationuser.oauth.oauth2.global.OAuthUserService; import com.nowait.domaincorerdb.user.entity.User; -import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import lombok.RequiredArgsConstructor; @@ -25,7 +20,7 @@ @RequiredArgsConstructor @Slf4j public class CustomOAuth2UserService extends DefaultOAuth2UserService { - private final UserRepository userRepository; + private final OAuthUserService oAuthUserService; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { @@ -44,34 +39,8 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } // DB에 유저가 있는지 판단 - Optional foundUser = userRepository.findByEmail(oAuth2Response.getEmail()); - - // DB에 유저 없으면 - 회원가입 - if (foundUser.isEmpty()) { - - User user = User.builder() - .email(oAuth2Response.getEmail()) - .phoneNumber("") - .nickname(oAuth2Response.getNickName()) - .profileImage(oAuth2Response.getProfileImage()) - .socialType(SocialType.KAKAO) - .role(Role.USER) // 일반 유저 설정 - .storeId(0L) - .phoneEntered(false) - .isMarketingAgree(false) - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - userRepository.save(user); + User user = oAuthUserService.loadOrCreateUser(oAuth2Response); - return new CustomOAuth2User(user); - } else { - // DB에 유저 존재하면 - 로그인 진행 (이때 로그인 처리는 안하고, OAuth2LoginSuccessHandler에서 담당함) - User user = foundUser.get(); - - return new CustomOAuth2User(user); - } + return new CustomOAuth2User(user); } - } From c9b16bdab7c64895c12eb60cea7fda2232a2ec06 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:48:07 +0900 Subject: [PATCH 589/685] =?UTF-8?q?feat:=20=EC=95=B1=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/KakaoAppAuthController.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java new file mode 100644 index 00000000..b16897f3 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java @@ -0,0 +1,35 @@ +package com.nowait.applicationuser.oauth.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginRequest; +import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginResponse; +import com.nowait.applicationuser.oauth.oauth2.app.KakaoAppLoginService; +import com.nowait.common.api.ApiUtils; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/v2/app/oauth/kakao") +@RequiredArgsConstructor +public class KakaoAppAuthController { + + private final KakaoAppLoginService kakaoAppLoginService; + + @PostMapping("/login") + public ResponseEntity kakaoAppLogin(@RequestBody KakaoAppLoginRequest request) { + + KakaoAppLoginResponse response = kakaoAppLoginService.login(request); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success(response) + ); + } +} From 04224a75a847db311f3992a12bee3a63934a634d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:49:17 +0900 Subject: [PATCH 590/685] =?UTF-8?q?feat:=20=EC=95=B1=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/dto/app/KakaoAppLoginRequest.java | 13 +++++++++++ .../oauth/dto/app/KakaoAppLoginResponse.java | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java new file mode 100644 index 00000000..1e75732a --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.oauth.dto.app; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class KakaoAppLoginRequest { + private String kakaoAccessToken; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java new file mode 100644 index 00000000..0eb12408 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java @@ -0,0 +1,22 @@ +package com.nowait.applicationuser.oauth.dto.app; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class KakaoAppLoginResponse{ + private String accessToken; + private String refreshToken; + private Long userId; + private String email; + private String nickName; + private String profileImage; + private boolean phoneEntered; + private boolean marketingAgree; + private boolean isNewUser; +} From 8e781f2bb386dd2c07272bb792d9d17015744d1b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:49:39 +0900 Subject: [PATCH 591/685] =?UTF-8?q?feat:=20=EC=95=B1=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20KakaoAppLoginService?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth2/app/KakaoAppLoginService.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java new file mode 100644 index 00000000..7f312bf8 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java @@ -0,0 +1,69 @@ +package com.nowait.applicationuser.oauth.oauth2.app; + +import java.time.Instant; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginRequest; +import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginResponse; +import com.nowait.applicationuser.oauth.oauth2.global.AuthTokenService; +import com.nowait.applicationuser.oauth.oauth2.web.CustomOAuth2UserService; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class KakaoAppLoginService { + + private final CustomOAuth2UserService customOAuth2UserService; + private final ClientRegistrationRepository clientRegistrationRepository; + private final AuthTokenService authTokenService; + + public KakaoAppLoginResponse login(KakaoAppLoginRequest request) { + String kakaoAccessTokenValue = request.getKakaoAccessToken(); + + ClientRegistration kakaoRegistration = clientRegistrationRepository.findByRegistrationId("kakao"); + + if (kakaoRegistration == null) { + throw new OAuth2AuthenticationException("Kakao Client Registration not found."); + } + + OAuth2AccessToken kakaoAccessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + kakaoAccessTokenValue, + Instant.now(), + null + ); + + OAuth2UserRequest userRequest = new OAuth2UserRequest( + kakaoRegistration, + kakaoAccessToken + ); + + OAuth2User oAuth2User = customOAuth2UserService.loadUser(userRequest); + CustomOAuth2User customUser = (CustomOAuth2User) oAuth2User; + User user = customUser.getUser(); + + AuthTokenService.TokenResult tokenResult = authTokenService.issueTokens(user); + + return KakaoAppLoginResponse.builder() + .accessToken(tokenResult.getAccessToken()) + .refreshToken(tokenResult.getRefreshToken()) + .userId(user.getId()) + .email(user.getEmail()) + .nickName(user.getNickname()) + .profileImage(user.getProfileImage()) + .phoneEntered(user.getPhoneEntered()) + .marketingAgree(user.getIsMarketingAgree()) + .isNewUser(false) // 앱 로그인에서는 항상 false로 설정 + .build(); + } +} From f8d493e0c7db9e0ea0df7816a160ce77919a8904 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:49:54 +0900 Subject: [PATCH 592/685] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/dto/{ => web}/KaKaoResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/{ => web}/KaKaoResponse.java (95%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/KaKaoResponse.java similarity index 95% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/KaKaoResponse.java index 9456be8e..cffd5dca 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/KaKaoResponse.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.dto; +package com.nowait.applicationuser.oauth.dto.web; import java.util.Map; From 3706efa72bdaa54b80d4d3f5f5f373a9d3e946a4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 18:49:57 +0900 Subject: [PATCH 593/685] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/dto/{ => web}/OAuth2Response.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/{ => web}/OAuth2Response.java (89%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuth2Response.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/OAuth2Response.java similarity index 89% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuth2Response.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/OAuth2Response.java index fb07ec78..99aecf90 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuth2Response.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/OAuth2Response.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.dto; +package com.nowait.applicationuser.oauth.dto.web; public interface OAuth2Response { // 제공자 (ex. naver, kakao) From 48942cbc890ad545aa7508407806a2419eba6f4a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:08:22 +0900 Subject: [PATCH 594/685] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20AuthToken?= =?UTF-8?q?Service=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/oauth2/global/AuthTokenService.java | 69 ----------- .../token/service/AuthTokenService.java | 115 ++++++++++++++++++ .../token/service/TokenService.java | 63 ---------- 3 files changed, 115 insertions(+), 132 deletions(-) delete mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java delete mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java deleted file mode 100644 index 3dd9c9f5..00000000 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/AuthTokenService.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.nowait.applicationuser.oauth.oauth2.global; - -import java.time.LocalDateTime; -import java.util.Optional; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.nowait.applicationuser.security.jwt.JwtUtil; -import com.nowait.domaincorerdb.token.entity.Token; -import com.nowait.domaincorerdb.token.repository.TokenRepository; -import com.nowait.domaincorerdb.user.entity.User; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class AuthTokenService { - - private final JwtUtil jwtUtil; - private final TokenRepository tokenRepository; - - @Transactional - public TokenResult issueTokens(User user) { - Long userId = user.getId(); - String role = user.getRole().name(); - - String accessToken = jwtUtil.createAccessToken( - "accessToken", - userId, - role, - Boolean.TRUE.equals(user.getPhoneEntered()), - Boolean.TRUE.equals(user.getIsMarketingAgree()), - 60 * 60 * 2000L - ); // 2시간 - - String refreshToken = jwtUtil.createRefreshToken( - "refreshToken", - userId, - 30L * 24 * 60 * 60 * 1000L - ); // 30일 - - // refreshToken을 DB에 저장 or update - Optional tokenOptional = tokenRepository.findByUserId(user.getId()); - if (tokenOptional.isPresent()) { - Token token = tokenOptional.get(); - token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); - } else { - Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(token); - } - - return new TokenResult(accessToken, refreshToken); - } - - public static class TokenResult { - private final String accessToken; - private final String refreshToken; - - public TokenResult(String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } - - public String getAccessToken() { return accessToken; } - public String getRefreshToken() { return refreshToken; } - } -} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java new file mode 100644 index 00000000..dc5f3f61 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java @@ -0,0 +1,115 @@ +package com.nowait.applicationuser.token.service; + +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.security.exception.RefreshTokenNotFoundException; +import com.nowait.applicationuser.security.exception.TokenBadRequestException; +import com.nowait.applicationuser.security.jwt.JwtUtil; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.domaincorerdb.token.entity.Token; +import com.nowait.domaincorerdb.token.repository.TokenRepository; +import com.nowait.domaincorerdb.user.entity.User; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AuthTokenService { + + private final JwtUtil jwtUtil; + private final TokenRepository tokenRepository; + + @Transactional + public boolean validateToken(String token, Long userId){ + // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. + Optional savedToken = tokenRepository.findByUserId(userId); + + // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 + if (savedToken.isEmpty()){ + log.debug("DB에 현재 userId에 대응되는 리프레시 토큰이 없습니다"); + return false; + } + + // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 + if (!savedToken.get().getRefreshToken().equals(token)){ + log.debug("DB에 저장된 리프레시 토큰와 현재 전달받은 리프레시 토큰 일치하지 않습니다"); + return false; + } + + // 리프레시 토큰의 만료여부 확인 + if(jwtUtil.isExpired(token)){ + log.debug("리프레시 토큰이 만료된 토큰입니다"); + return false; // 만료된 토큰은 유효하지 않음 + } + + log.info("리프레시 토큰이 유효한 토큰입니다"); + return true; // 모든 조건 만족 시, 유효한 토큰 + } + + @Transactional + public AuthenticationResponse issueTokens(User user) { + Long userId = user.getId(); + String role = user.getRole().name(); + + String accessToken = jwtUtil.createAccessToken( + "accessToken", + userId, + role, + Boolean.TRUE.equals(user.getPhoneEntered()), + Boolean.TRUE.equals(user.getIsMarketingAgree()), + 60 * 60 * 2000L + ); // 2시간 + + String refreshToken = jwtUtil.createRefreshToken( + "refreshToken", + userId, + 30L * 24 * 60 * 60 * 1000L + ); // 30일 + + newUpdateRefreshToken(user, refreshToken); + return new AuthenticationResponse(accessToken, refreshToken); + } + + @Transactional + public void newUpdateRefreshToken(User user, String refreshToken) { + Optional tokenOptional = tokenRepository.findByUserId(user.getId()); + if (tokenOptional.isPresent()) { + Token token = tokenOptional.get(); + token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); + } else { + Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); + tokenRepository.save(token); + } + } + + @Transactional + public void updateRefreshToken(Long userId, String oldRefreshToken, String newRefreshToken){ + Token token = tokenRepository.findByUserId(userId) + .orElseThrow(RefreshTokenNotFoundException::new); // 404 + + if (!token.getRefreshToken().equals(oldRefreshToken)){ + throw new TokenBadRequestException(); // 400 + } + + // 기존 토큰 삭제 및 새 토큰 저장 + token.updateRefreshToken(newRefreshToken, LocalDateTime.now().plusDays(30)); + } + + @Getter + public static class TokenResult { + private final String accessToken; + private final String refreshToken; + + public TokenResult(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java deleted file mode 100644 index eee87b82..00000000 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.nowait.applicationuser.token.service; - -import java.time.LocalDateTime; -import java.util.Optional; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.nowait.applicationuser.security.exception.RefreshTokenNotFoundException; -import com.nowait.applicationuser.security.exception.TokenBadRequestException; -import com.nowait.applicationuser.security.jwt.JwtUtil; -import com.nowait.domaincorerdb.token.entity.Token; -import com.nowait.domaincorerdb.token.repository.TokenRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Service -@RequiredArgsConstructor -@Slf4j -public class TokenService { - private final TokenRepository tokenRepository; - private final JwtUtil jwtUtil; - - @Transactional - public boolean validateToken(String token, Long userId){ - // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. - Optional savedToken = tokenRepository.findByUserId(userId); - - // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 - if (savedToken.isEmpty()){ - log.debug("DB에 현재 userId에 대응되는 리프레시 토큰이 없습니다"); - return false; - } - - // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 - if (!savedToken.get().getRefreshToken().equals(token)){ - log.debug("DB에 저장된 리프레시 토큰와 현재 전달받은 리프레시 토큰 일치하지 않습니다"); - return false; - } - - // 리프레시 토큰의 만료여부 확인 - if(jwtUtil.isExpired(token)){ - log.debug("리프레시 토큰이 만료된 토큰입니다"); - return false; // 만료된 토큰은 유효하지 않음 - } - - log.info("리프레시 토큰이 유효한 토큰입니다"); - return true; // 모든 조건 만족 시, 유효한 토큰 - } - - @Transactional - public void updateRefreshToken(Long userId, String oldRefreshToken, String newRefreshToken){ - Token token = tokenRepository.findByUserId(userId) - .orElseThrow(RefreshTokenNotFoundException::new); // 404 - - if (!token.getRefreshToken().equals(oldRefreshToken)){ - throw new TokenBadRequestException(); // 400 - } - - // 기존 토큰 삭제 및 새 토큰 저장 - token.updateRefreshToken(newRefreshToken, LocalDateTime.now().plusDays(30)); - } -} From 2a43db3b05961dc0d79ce49f0dc19b2773656963 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:08:54 +0900 Subject: [PATCH 595/685] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/UserService.java | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java index 38496b9f..5820b224 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -7,8 +7,7 @@ import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.dto.NewAccessTokenResponse; -import com.nowait.applicationuser.token.service.TokenService; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; @@ -20,15 +19,13 @@ public class UserService { private final UserRepository userRepository; - private final TokenService tokenService; + private final AuthTokenService authTokenService; private final JwtUtil jwtUtil; @Transactional - public NewAccessTokenResponse putOptional(String phoneNumber, boolean consent, String accessToken) { + public AuthenticationResponse putOptional(String phoneNumber, boolean consent, String accessToken) { - Long userId = jwtUtil.getUserId(accessToken);; - String role = jwtUtil.getRole(accessToken); - AuthenticationResponse authenticationResponse; + Long userId = jwtUtil.getUserId(accessToken); User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); @@ -39,17 +36,6 @@ public NewAccessTokenResponse putOptional(String phoneNumber, boolean consent, S user.setPhoneNumberAndMarkEntered(phoneNumber, LocalDateTime.now()); user.setIsMarketingAgree(consent, LocalDateTime.now()); - String newAccessToken = jwtUtil.createAccessToken( - "accessToken", - userId, - role, - Boolean.TRUE.equals(user.getPhoneEntered()), - Boolean.TRUE.equals(user.getIsMarketingAgree()), - 60 * 60 * 1000L - ); - - NewAccessTokenResponse newAccessTokenResponse = new NewAccessTokenResponse(newAccessToken); - - return newAccessTokenResponse; + return authTokenService.issueTokens(user); } } From f9dc1ed2c5b09ce4a244cd433371ae120fd3a6af Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:10:07 +0900 Subject: [PATCH 596/685] =?UTF-8?q?refactor:=20dto=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{web => }/OAuth2LoginSuccessHandler.java | 11 ++++++----- .../app => service}/KakaoAppLoginService.java | 16 ++++++++-------- .../user/controller/UserController.java | 6 ++---- 3 files changed, 16 insertions(+), 17 deletions(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/{web => }/OAuth2LoginSuccessHandler.java (84%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/{oauth2/app => service}/KakaoAppLoginService.java (80%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java similarity index 84% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/OAuth2LoginSuccessHandler.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 584f29e6..b741f9be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.oauth2.web; +package com.nowait.applicationuser.oauth.oauth2; import java.io.IOException; @@ -8,7 +8,8 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationuser.oauth.oauth2.global.AuthTokenService; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -36,10 +37,10 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); User user = customUserDetails.getUser(); - AuthTokenService.TokenResult tokenResult = authTokenService.issueTokens(user); + AuthenticationResponse authenticationResponse = authTokenService.issueTokens(user); // 2. refreshToken을 HttpOnly 쿠키로 설정 (ResponseCookie로) - ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", tokenResult.getRefreshToken()) + ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", authenticationResponse.getRefreshToken()) .httpOnly(true) .secure(false) // 운영환경에서는 true .path("/") @@ -51,7 +52,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) - String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + tokenResult.getAccessToken(); + String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + authenticationResponse.getAccessToken(); response.sendRedirect(targetUrl); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java similarity index 80% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java index 7f312bf8..29dbdd63 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/app/KakaoAppLoginService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.oauth2.app; +package com.nowait.applicationuser.oauth.service; import java.time.Instant; @@ -10,10 +10,10 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginRequest; -import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginResponse; -import com.nowait.applicationuser.oauth.oauth2.global.AuthTokenService; -import com.nowait.applicationuser.oauth.oauth2.web.CustomOAuth2UserService; +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginRequest; +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginResponse; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -52,11 +52,11 @@ public KakaoAppLoginResponse login(KakaoAppLoginRequest request) { CustomOAuth2User customUser = (CustomOAuth2User) oAuth2User; User user = customUser.getUser(); - AuthTokenService.TokenResult tokenResult = authTokenService.issueTokens(user); + AuthenticationResponse authenticationResponse = authTokenService.issueTokens(user); return KakaoAppLoginResponse.builder() - .accessToken(tokenResult.getAccessToken()) - .refreshToken(tokenResult.getRefreshToken()) + .accessToken(authenticationResponse.getAccessToken()) + .refreshToken(authenticationResponse.getRefreshToken()) .userId(user.getId()) .email(user.getEmail()) .nickName(user.getNickname()) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index b0179d21..91ab09ca 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -2,14 +2,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.dto.NewAccessTokenResponse; import com.nowait.applicationuser.user.dto.UserUpdateRequest; import com.nowait.applicationuser.user.service.UserService; import com.nowait.common.api.ApiUtils; @@ -28,14 +26,14 @@ public class UserController { public ResponseEntity putOptional( @Valid @RequestBody UserUpdateRequest req) { - NewAccessTokenResponse newAccessTokenResponse = userService.putOptional(req.phoneNumber(), + AuthenticationResponse authenticationResponse = userService.putOptional(req.phoneNumber(), Boolean.TRUE.equals(req.consent()), req.accessToken()); return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - newAccessTokenResponse + authenticationResponse ) ); } From aafabeddd4666050f874f5019c9014c867a5f4b7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:10:39 +0900 Subject: [PATCH 597/685] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/config/security/SecurityConfig.java | 4 ++-- .../oauth/controller/KakaoAppAuthController.java | 6 +++--- .../applicationuser/oauth/dto/{web => }/KaKaoResponse.java | 2 +- .../oauth/dto/{app => }/KakaoAppLoginRequest.java | 2 +- .../oauth/dto/{app => }/KakaoAppLoginResponse.java | 2 +- .../oauth/dto/{web => }/OAuth2Response.java | 2 +- .../{oauth2/web => service}/CustomOAuth2UserService.java | 7 +++---- .../oauth/{oauth2/global => service}/OAuthUserService.java | 4 ++-- 8 files changed, 14 insertions(+), 15 deletions(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/{web => }/KaKaoResponse.java (95%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/{app => }/KakaoAppLoginRequest.java (84%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/{app => }/KakaoAppLoginResponse.java (90%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/{web => }/OAuth2Response.java (89%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/{oauth2/web => service}/CustomOAuth2UserService.java (86%) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/{oauth2/global => service}/OAuthUserService.java (90%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 8e717a86..20e844e9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -14,8 +14,8 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; -import com.nowait.applicationuser.oauth.oauth2.web.CustomOAuth2UserService; -import com.nowait.applicationuser.oauth.oauth2.web.OAuth2LoginSuccessHandler; +import com.nowait.applicationuser.oauth.service.CustomOAuth2UserService; +import com.nowait.applicationuser.oauth.oauth2.OAuth2LoginSuccessHandler; import com.nowait.applicationuser.security.jwt.JwtAuthorizationFilter; import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.security.service.CustomUserDetailService; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java index b16897f3..0b744df0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java @@ -7,9 +7,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginRequest; -import com.nowait.applicationuser.oauth.dto.app.KakaoAppLoginResponse; -import com.nowait.applicationuser.oauth.oauth2.app.KakaoAppLoginService; +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginRequest; +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginResponse; +import com.nowait.applicationuser.oauth.service.KakaoAppLoginService; import com.nowait.common.api.ApiUtils; import lombok.RequiredArgsConstructor; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/KaKaoResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java similarity index 95% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/KaKaoResponse.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java index cffd5dca..9456be8e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/KaKaoResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KaKaoResponse.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.dto.web; +package com.nowait.applicationuser.oauth.dto; import java.util.Map; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginRequest.java similarity index 84% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginRequest.java index 1e75732a..241b8604 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginRequest.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginRequest.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.dto.app; +package com.nowait.applicationuser.oauth.dto; import lombok.AccessLevel; import lombok.AllArgsConstructor; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginResponse.java similarity index 90% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginResponse.java index 0eb12408..4c6f36b5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/app/KakaoAppLoginResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginResponse.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.dto.app; +package com.nowait.applicationuser.oauth.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/OAuth2Response.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuth2Response.java similarity index 89% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/OAuth2Response.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuth2Response.java index 99aecf90..fb07ec78 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/web/OAuth2Response.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuth2Response.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.dto.web; +package com.nowait.applicationuser.oauth.dto; public interface OAuth2Response { // 제공자 (ex. naver, kakao) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java similarity index 86% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/CustomOAuth2UserService.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java index 7459252a..ca2928f7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/web/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java @@ -1,4 +1,4 @@ -package com.nowait.applicationuser.oauth.oauth2.web; +package com.nowait.applicationuser.oauth.service; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -6,9 +6,8 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import com.nowait.applicationuser.oauth.dto.web.KaKaoResponse; -import com.nowait.applicationuser.oauth.dto.web.OAuth2Response; -import com.nowait.applicationuser.oauth.oauth2.global.OAuthUserService; +import com.nowait.applicationuser.oauth.dto.KaKaoResponse; +import com.nowait.applicationuser.oauth.dto.OAuth2Response; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java similarity index 90% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java index 4fafea6b..e51eb5bd 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/global/OAuthUserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java @@ -1,11 +1,11 @@ -package com.nowait.applicationuser.oauth.oauth2.global; +package com.nowait.applicationuser.oauth.service; import java.time.LocalDateTime; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationuser.oauth.dto.web.OAuth2Response; +import com.nowait.applicationuser.oauth.dto.OAuth2Response; import com.nowait.common.enums.Role; import com.nowait.common.enums.SocialType; import com.nowait.domaincorerdb.user.entity.User; From fc4a4bfd641651e7d300d2901661824f4abe64a9 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:10:48 +0900 Subject: [PATCH 598/685] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20AuthToken?= =?UTF-8?q?Service=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/token/controller/TokenController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index 53368747..25a79ba9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -10,7 +10,7 @@ import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.service.TokenService; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; @@ -26,7 +26,7 @@ @Slf4j public class TokenController { private final JwtUtil jwtUtil; - private final TokenService tokenService; + private final AuthTokenService tokenService; private final UserRepository userRepository; @Value("${jwt.access-token-expiration-ms}") private long accessTokenExpiration; From 5d05abf275bda57e1134aba38d37078f3ce05cff Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:10:51 +0900 Subject: [PATCH 599/685] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20AuthToken?= =?UTF-8?q?Service=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../token/dto/NewAccessTokenResponse.java | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java deleted file mode 100644 index f42e7979..00000000 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.nowait.applicationuser.token.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -@RequiredArgsConstructor -@Getter -@ToString(exclude = {"accessToken"}) // 로깅 시 토큰 노출 방지 -public class NewAccessTokenResponse { - @JsonProperty("access_token") - private final String accessToken; -} From a0e362a9971651899b1a12a8a33e13423b482939 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:18:44 +0900 Subject: [PATCH 600/685] =?UTF-8?q?todo:=20=EC=82=AC=EC=9A=A9=EC=B2=98=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/token/dto/RefreshTokenRequest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java index c0bf4e68..ac4b9c5c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +// TODO : 사용하는 DTO인지 확인 필요 @Getter @NoArgsConstructor public class RefreshTokenRequest { From 375b79f30a84f3b80b75555d256159dc768dedf4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:38:25 +0900 Subject: [PATCH 601/685] =?UTF-8?q?refactor:=20=EC=8B=A0=EA=B7=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/dto/OAuthUserResult.java | 13 +++++++++++++ .../oauth/service/CustomOAuth2UserService.java | 7 +++++-- .../oauth/service/KakaoAppLoginService.java | 2 +- .../oauth/service/OAuthUserService.java | 9 +++++++-- .../oauth/dto/CustomOAuth2User.java | 16 ++++++++++------ 5 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java new file mode 100644 index 00000000..01532eb5 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.oauth.dto; + +import com.nowait.domaincorerdb.user.entity.User; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class OAuthUserResult { + private final User user; + private final boolean newUser; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java index ca2928f7..8171facf 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java @@ -8,6 +8,7 @@ import com.nowait.applicationuser.oauth.dto.KaKaoResponse; import com.nowait.applicationuser.oauth.dto.OAuth2Response; +import com.nowait.applicationuser.oauth.dto.OAuthUserResult; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -38,8 +39,10 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } // DB에 유저가 있는지 판단 - User user = oAuthUserService.loadOrCreateUser(oAuth2Response); + OAuthUserResult result = oAuthUserService.loadOrCreateUser(oAuth2Response); + User user = result.getUser(); + boolean newUser = result.isNewUser(); - return new CustomOAuth2User(user); + return new CustomOAuth2User(user, newUser); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java index 29dbdd63..dddb4fdb 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java @@ -63,7 +63,7 @@ public KakaoAppLoginResponse login(KakaoAppLoginRequest request) { .profileImage(user.getProfileImage()) .phoneEntered(user.getPhoneEntered()) .marketingAgree(user.getIsMarketingAgree()) - .isNewUser(false) // 앱 로그인에서는 항상 false로 설정 + .isNewUser(customUser.isNewUser()) .build(); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java index e51eb5bd..96f5063f 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java @@ -6,6 +6,7 @@ import org.springframework.transaction.annotation.Transactional; import com.nowait.applicationuser.oauth.dto.OAuth2Response; +import com.nowait.applicationuser.oauth.dto.OAuthUserResult; import com.nowait.common.enums.Role; import com.nowait.common.enums.SocialType; import com.nowait.domaincorerdb.user.entity.User; @@ -19,9 +20,13 @@ public class OAuthUserService { private final UserRepository userRepository; @Transactional - public User loadOrCreateUser(OAuth2Response oAuth2Response) { + public OAuthUserResult loadOrCreateUser(OAuth2Response oAuth2Response) { return userRepository.findByEmail(oAuth2Response.getEmail()) - .orElseGet(() -> createUser(oAuth2Response)); + .map(user -> new OAuthUserResult(user, false)) // 기존 유저 + .orElseGet(() -> { + User created = createUser(oAuth2Response); + return new OAuthUserResult(created, true); // 신규 유저 + }); } private User createUser(OAuth2Response oAuth2Response) { diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java index f163bca7..544c91c7 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java @@ -9,16 +9,14 @@ import com.nowait.domaincorerdb.user.entity.User; +import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor +@Getter public class CustomOAuth2User implements OAuth2User { - private User user; - - // User 객체를 받는 생성자 - public CustomOAuth2User(User user) { - this.user = user; - } + private final User user; + private final boolean newUser; @Override public Map getAttributes() { @@ -40,6 +38,12 @@ public String getAuthority() { return authorities; } + // JWT 인증 시 (항상 기존 유저 취급) + public CustomOAuth2User(User user) { + this.user = user; + this.newUser = false; + } + @Override public String getName() { return user.getEmail(); From 9d5ec3230a6afdb4e3b0091f67b3614e991e90d6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:41:46 +0900 Subject: [PATCH 602/685] =?UTF-8?q?refactor:=20=EC=8B=A0=EA=B7=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/service/CustomOAuth2UserService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java index 8171facf..09b29e9a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java @@ -26,6 +26,7 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); + // TODO : 해당 로그 필요한지 추후 확인 필요 log.info("CustomOAuth2UserService :: {}", oAuth2User); log.info("oAuthUser.getAttributes :: {}", oAuth2User.getAttributes()); From 7bb68e1a17e051cc3234630172a947d6fbe46125 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 15 Dec 2025 22:42:52 +0900 Subject: [PATCH 603/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/oauth/service/KakaoAppLoginService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java index dddb4fdb..722dd223 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java @@ -30,6 +30,10 @@ public class KakaoAppLoginService { public KakaoAppLoginResponse login(KakaoAppLoginRequest request) { String kakaoAccessTokenValue = request.getKakaoAccessToken(); + if (kakaoAccessTokenValue == null || kakaoAccessTokenValue.isEmpty()) { + throw new OAuth2AuthenticationException("Kakao Access Token is missing."); + } + ClientRegistration kakaoRegistration = clientRegistrationRepository.findByRegistrationId("kakao"); if (kakaoRegistration == null) { From 6940ec7bfaf9555d101b55ba0b9e2c9b0b5f7977 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 21:35:48 +0900 Subject: [PATCH 604/685] =?UTF-8?q?refactor:=20api=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EB=AA=85=20=EC=9D=BC=EA=B4=80=ED=99=94=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/bookmark/controller/BookmarkController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java index 5c1e9268..25e6bbb4 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java @@ -22,7 +22,7 @@ @Tag(name = "Bookmark API", description = "북마크 API") @RestController -@RequestMapping("/bookmarks") +@RequestMapping("v1/users/me/bookmarks") @RequiredArgsConstructor public class BookmarkController { private final BookmarkService bookmarkService; From f357cdbfd6f6d37cd09f84db6aedaec758d1ea5f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:08:35 +0900 Subject: [PATCH 605/685] =?UTF-8?q?refactor:=20api=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EB=AA=85=20=EC=9D=BC=EA=B4=80=ED=99=94=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 6 +- .../controller/KakaoAppAuthController.java | 2 +- .../order/controller/OrderController.java | 6 +- .../controller/ReservationController.java | 15 +++-- .../store/controller/StoreController.java | 15 ++--- .../controller/StorePaymentController.java | 58 +++++++++---------- .../token/controller/TokenController.java | 2 +- .../user/controller/UserController.java | 11 ++-- 8 files changed, 55 insertions(+), 60 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java index 9c8b997a..bd446191 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/controller/MenuController.java @@ -18,14 +18,14 @@ @Tag(name = "Menu API", description = "메뉴 API") @RestController -@RequestMapping("/v1/menus") +@RequestMapping("/v1/stores/{publicCode}/menus") @RequiredArgsConstructor @Slf4j public class MenuController { private final MenuService menuService; - @GetMapping("/all-menus/stores/{publicCode}") + @GetMapping @Operation(summary = "가게의 모든 메뉴 조회", description = "특정 가게의 모든 메뉴를 조회합니다.") @ApiResponse(responseCode = "200", description = "모든 메뉴를 조회 성공") public ResponseEntity getMenusByStoreId(@PathVariable String publicCode) { @@ -38,7 +38,7 @@ public ResponseEntity getMenusByStoreId(@PathVariable String publicCode) { ); } - @GetMapping("/{publicCode}/{menuId}") + @GetMapping("/{menuId}") @Operation( summary = "메뉴 ID로 메뉴 조회", description = "특정 가게의 특정 메뉴를 ID로 조회합니다.") @ApiResponse(responseCode = "200", description = "메뉴 조회 성공") diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java index 0b744df0..18dac6ae 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java @@ -15,7 +15,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/v2/app/oauth/kakao") +@RequestMapping("/v1/auth/app/kakao") @RequiredArgsConstructor public class KakaoAppAuthController { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 76501d55..8a50297d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -27,7 +27,7 @@ @Tag(name = "Order API", description = "주문 API") @RestController -@RequestMapping("/orders") +@RequestMapping("/v1/stores/{publicCode}/tables/{tableId}/orders") @RequiredArgsConstructor public class OrderController { private final OrderService orderService; @@ -50,7 +50,7 @@ public class OrderController { // ); // } - @PostMapping("/create/{publicCode}/{tableId}") + @PostMapping @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") @ApiResponse(responseCode = "201", description = "주문 생성") public ResponseEntity createOrder( @@ -69,7 +69,7 @@ public ResponseEntity createOrder( ); } - @GetMapping("/items/{publicCode}/{tableId}") + @GetMapping @Operation(summary = "테이블별 주문 아이템 조회", description = "비로그인(세션) 기준으로 테이블의 내 주문 목록만 조회") @ApiResponse(responseCode = "200", description = "주문 조회") public ResponseEntity getOrderItems( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 6c1dfe99..fa00e0e3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -28,13 +28,15 @@ @Tag(name = "Reservation API", description = "예약 API") @RestController -@RequestMapping("/reservations") +@RequestMapping("/v1/users/me/waitings") @RequiredArgsConstructor public class ReservationController { private final ReservationService reservationService; - @PostMapping("/create/{storeId}") + // 성능 비교를 위해 남겨둔 레거시 웨이팅 등록 API + @PostMapping("/legacy/{storeId}") + @Operation(summary = "웨이팅 등록", description = "특정 주점에 대한 웨이팅 등록 레거시") public ResponseEntity create( @PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User, @@ -49,7 +51,7 @@ public ResponseEntity create( ); } - @PostMapping("/create/redis/{storeId}") + @PostMapping("/{storeId}") @Operation(summary = "대기열 등록", description = "특정 주점에 대한 대기열 등록") @ApiResponse(responseCode = "201", description = "대기열 등록") public ResponseEntity createQueue( @@ -67,7 +69,7 @@ public ResponseEntity createQueue( ); } - @GetMapping("/get/queue/redis/{storeId}") + @GetMapping("/{storeId}") @Operation(summary = "특정 주점의 본인 대기열 조회", description = "특정 주점에 대한 본인 대기열 조회") @ApiResponse(responseCode = "200", description = "본인 대기열 조회") public ResponseEntity getQueue( @@ -84,7 +86,8 @@ public ResponseEntity getQueue( ); } - @DeleteMapping("/delete/queue/redis/{storeId}") + // TODO : 대기열 취소 시 예약 번호 사용하도록 변경 (현재는 주점 아이디로 처리 중) + @DeleteMapping("{storeId}") @Operation(summary = "내 대기열 취소", description = "특정 주점에 대한 대기열 취소") @ApiResponse(responseCode = "200", description = "대기열 취소") public ResponseEntity deleteQueue( @@ -100,7 +103,7 @@ public ResponseEntity deleteQueue( ); } - @GetMapping("/my/waitings") + @GetMapping @Operation(summary = "내 모든 대기열 리스트 확인", description = "내가 신청한 모든 대기열 리스트 확인") @ApiResponse(responseCode = "200", description = "대기열 리스트 조회") public ResponseEntity getAllMyWaitings(@AuthenticationPrincipal CustomOAuth2User customOAuth2User) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index 8eefa2d7..d951926a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -22,7 +22,7 @@ @Tag(name = "Store API", description = "주점(Store) API") @RestController -@RequestMapping("v1/stores") +@RequestMapping("/v1/stores") @RequiredArgsConstructor @Slf4j public class StoreController { @@ -30,13 +30,10 @@ public class StoreController { private final StoreService storeService; - @GetMapping("/all-stores") - @Operation( - summary = "모든 주점 페이지네이션 조회", - description = "모든 주점을 페이지네이션으로 조회합니다." - ) + @GetMapping + @Operation(summary = "모든 주점 페이지네이션 조회", description = "모든 주점을 페이지네이션으로 조회합니다.") @ApiResponse(responseCode = "200", description = "모든 주점 페이지네이션 조회 성공") - public ResponseEntity getAllStoresByPageAndDeparments(Pageable pageable, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { + public ResponseEntity getAllStoresByPageAndDepartments(Pageable pageable, @AuthenticationPrincipal CustomOAuth2User customOAuth2User) { return ResponseEntity .ok() .body( @@ -59,7 +56,7 @@ public ResponseEntity getStoreById(@PathVariable String publicCode, @Authenti ); } - @GetMapping("/search") + @GetMapping @Operation(summary = "주점 이름으로 주점 검색", description = "주점 이름을 기준으로 주점을 검색합니다.") @ApiResponse(responseCode = "200", description = "주점 검색 성공") public ResponseEntity searchStores(@RequestParam("keyword") String keyword) { @@ -72,7 +69,7 @@ public ResponseEntity searchStores(@RequestParam("keyword") String keyword) { ); } - @GetMapping("/waiting-list") + @GetMapping("/waiting-count") @Operation(summary = "예약 많은순/적은순 주점 리스트 조회", description = "desc(대기 많은순) , asc(대기 적은순)") @ApiResponse(responseCode = "200", description = "주점 대기순 정렬") public ResponseEntity getStoreWaitingList( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index c8cd09c4..25cbd3ea 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -19,37 +19,31 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -@Tag(name = "Store Payment API", description = "주점 결제 정보 API") -@RestController -@RequestMapping("v1/store-payments") -@RequiredArgsConstructor -@Slf4j -public class StorePaymentController { - - private final StorePaymentService storePaymentService; - - @GetMapping(("/{publicCode}")) - @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") - @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") - public ResponseEntity getStorePaymentByStoreId(@PathVariable String publicCode) { - Optional response = storePaymentService.getStorePaymentByStoreId(publicCode); - - if (response.isPresent()) { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - response - ) - ); - } else { - return ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - "해당 주점의 등록된 결제 정보가 존재하지 않습니다." - ) - ); + @Tag(name = "Store Payment API", description = "주점 결제 정보 API") + @RestController + @RequestMapping("/v1/store//{publicCode}/payments") + @RequiredArgsConstructor + @Slf4j + public class StorePaymentController { + + private final StorePaymentService storePaymentService; + + @GetMapping + @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") + @ApiResponse(responseCode = "204", description = "결제 정보 없음") + public ResponseEntity getStorePaymentByStoreId(@PathVariable String publicCode) { + + return storePaymentService.getStorePaymentByStoreId(publicCode) + .map(response -> ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + )) + .orElseGet(() -> ResponseEntity + .status(HttpStatus.NO_CONTENT).build() + ); } } -} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index 25a79ba9..211c9330 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -22,7 +22,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/refresh-token") +@RequestMapping("/v1/auth/token/refresh") @Slf4j public class TokenController { private final JwtUtil jwtUtil; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index 91ab09ca..79f0906b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -16,18 +16,19 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/users") +@RequestMapping("/v1/users/me") @RequiredArgsConstructor public class UserController { private final UserService userService; - @PutMapping("/optional-info") + // TODO : Pathch로 변경 고려 및 토큰 전달 방식 변경 고려 (SecurityContext) + @PutMapping("/optional") public ResponseEntity putOptional( - @Valid @RequestBody UserUpdateRequest req) { + @Valid @RequestBody UserUpdateRequest request) { - AuthenticationResponse authenticationResponse = userService.putOptional(req.phoneNumber(), - Boolean.TRUE.equals(req.consent()), req.accessToken()); + AuthenticationResponse authenticationResponse = userService.putOptional(request.phoneNumber(), + Boolean.TRUE.equals(request.consent()), request.accessToken()); return ResponseEntity .status(HttpStatus.OK) From d5584736f98d75e80c69f1cd40bc0151d53f97fe Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:10:28 +0900 Subject: [PATCH 606/685] =?UTF-8?q?refactor:=20api=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EB=AA=85=20=EC=9D=BC=EA=B4=80=ED=99=94=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storepayment/controller/StorePaymentController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index 25cbd3ea..e308f653 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -21,7 +21,7 @@ @Tag(name = "Store Payment API", description = "주점 결제 정보 API") @RestController - @RequestMapping("/v1/store//{publicCode}/payments") + @RequestMapping("/v1/store/{publicCode}/payments") @RequiredArgsConstructor @Slf4j public class StorePaymentController { From 9bf5a45d0e1241cb9d05a2fca5a0611c5d47a777 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:26:20 +0900 Subject: [PATCH 607/685] =?UTF-8?q?refactor:=20api=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EB=AA=85=20=EC=9D=BC=EA=B4=80=ED=99=94=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/store/controller/StoreController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java index d951926a..2d085022 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/controller/StoreController.java @@ -56,7 +56,7 @@ public ResponseEntity getStoreById(@PathVariable String publicCode, @Authenti ); } - @GetMapping + @GetMapping("/search") @Operation(summary = "주점 이름으로 주점 검색", description = "주점 이름을 기준으로 주점을 검색합니다.") @ApiResponse(responseCode = "200", description = "주점 검색 성공") public ResponseEntity searchStores(@RequestParam("keyword") String keyword) { From 2fa79cc513341fbddfc52881682c6512e5f3a21f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:33:18 +0900 Subject: [PATCH 608/685] =?UTF-8?q?refactor:=20=EA=B2=BD=EB=A1=9C=EC=97=90?= =?UTF-8?q?=20=EC=84=A0=ED=96=89=20=EC=8A=AC=EB=9E=98=EC=8B=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/bookmark/controller/BookmarkController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java index 25e6bbb4..01fc1774 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/bookmark/controller/BookmarkController.java @@ -22,7 +22,7 @@ @Tag(name = "Bookmark API", description = "북마크 API") @RestController -@RequestMapping("v1/users/me/bookmarks") +@RequestMapping("/v1/users/me/bookmarks") @RequiredArgsConstructor public class BookmarkController { private final BookmarkService bookmarkService; From 64d135b6deb029e8f856864f4d5458d51cf4bfed Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:34:49 +0900 Subject: [PATCH 609/685] =?UTF-8?q?refactor:=20=EA=B2=BD=EB=A1=9C=EC=97=90?= =?UTF-8?q?=20=EC=84=A0=ED=96=89=20=EC=8A=AC=EB=9E=98=EC=8B=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/controller/ReservationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index fa00e0e3..845784c8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -87,7 +87,7 @@ public ResponseEntity getQueue( } // TODO : 대기열 취소 시 예약 번호 사용하도록 변경 (현재는 주점 아이디로 처리 중) - @DeleteMapping("{storeId}") + @DeleteMapping("/{storeId}") @Operation(summary = "내 대기열 취소", description = "특정 주점에 대한 대기열 취소") @ApiResponse(responseCode = "200", description = "대기열 취소") public ResponseEntity deleteQueue( From cd73750482609704e93c5e6f75c8547040b05cbd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:35:17 +0900 Subject: [PATCH 610/685] =?UTF-8?q?refactor:=20=EB=93=A4=EC=97=AC=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StorePaymentController.java | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index e308f653..30686cfc 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -19,31 +19,31 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; - @Tag(name = "Store Payment API", description = "주점 결제 정보 API") - @RestController - @RequestMapping("/v1/store/{publicCode}/payments") - @RequiredArgsConstructor - @Slf4j - public class StorePaymentController { - - private final StorePaymentService storePaymentService; - - @GetMapping - @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") - @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") - @ApiResponse(responseCode = "204", description = "결제 정보 없음") - public ResponseEntity getStorePaymentByStoreId(@PathVariable String publicCode) { - - return storePaymentService.getStorePaymentByStoreId(publicCode) - .map(response -> ResponseEntity - .status(HttpStatus.OK) - .body( - ApiUtils.success( - response - ) - )) - .orElseGet(() -> ResponseEntity - .status(HttpStatus.NO_CONTENT).build() - ); - } +@Tag(name = "Store Payment API", description = "주점 결제 정보 API") +@RestController +@RequestMapping("/v1/store/{publicCode}/payments") +@RequiredArgsConstructor +@Slf4j +public class StorePaymentController { + + private final StorePaymentService storePaymentService; + + @GetMapping + @Operation(summary = "주점 결제 정보 조회", description = "주점 ID로 주점 결제 정보를 조회합니다.") + @ApiResponse(responseCode = "200", description = "주점 결제 정보 조회 성공") + @ApiResponse(responseCode = "204", description = "결제 정보 없음") + public ResponseEntity getStorePaymentByStoreId(@PathVariable String publicCode) { + + return storePaymentService.getStorePaymentByStoreId(publicCode) + .map(response -> ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + ) + )) + .orElseGet(() -> ResponseEntity + .status(HttpStatus.NO_CONTENT).build() + ); } +} From 57e2e5ac1b36a0014ce19ac2682f05b2529c1306 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:36:00 +0900 Subject: [PATCH 611/685] =?UTF-8?q?refactor:=20=EA=B2=BD=EB=A1=9C=EB=AA=85?= =?UTF-8?q?=20=EC=9D=BC=EA=B4=80=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storepayment/controller/StorePaymentController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java index 30686cfc..cd1d8da0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/storepayment/controller/StorePaymentController.java @@ -21,7 +21,7 @@ @Tag(name = "Store Payment API", description = "주점 결제 정보 API") @RestController -@RequestMapping("/v1/store/{publicCode}/payments") +@RequestMapping("/v1/stores/{publicCode}/payments") @RequiredArgsConstructor @Slf4j public class StorePaymentController { From 80783644b9d1d4e024996af1d85218e514b636b6 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:39:40 +0900 Subject: [PATCH 612/685] =?UTF-8?q?refactor:=20SecurityConfig=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/config/security/SecurityConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 20e844e9..802f72e7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -57,10 +57,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers( "/oauth2/authorization/kakao", // 카카오 로그인 요청 "/login/oauth2/code/**", // 카카오 인증 콜백 - "/api/refresh-token", // refresh token (토큰 갱신) - "/v2/app/oauth/kakao/login", // 카카오 앱 로그인 - "/v1/menus/**", // 모든 메뉴 조회 - "/v1/store-payments/**", // 결제 관련 API + "/v1/auth/token/refresh", // refresh token (토큰 갱신) + "/v1/auth/app/kakao/login", // 카카오 앱 로그인 + "/v1/stores/*/menus/**", // 모든 메뉴 조회 + "/v1/stores/*/payments/**", // 결제 관련 API "/orders/**", "/swagger-ui/**", "/v3/api-docs/**", From 54ced6de68bb36c303c4f5e155a3b270035f98c7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 21 Dec 2025 22:41:35 +0900 Subject: [PATCH 613/685] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/controller/OrderController.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java index 8a50297d..df3d2114 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/controller/OrderController.java @@ -32,24 +32,6 @@ public class OrderController { private final OrderService orderService; - // @PostMapping("/create/{storeId}/{tableId}") - // @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") - // @ApiResponse(responseCode = "201", description = "주문 생성") - // public ResponseEntity createOrder( - // @PathVariable Long storeId, - // @PathVariable Long tableId, - // @RequestBody @Valid OrderCreateRequestDto orderCreateRequestDto, - // HttpSession session - // ) { - // String sessionId = session.getId(); - // OrderCreateResponseDto response = orderService.createOrder(storeId, tableId, orderCreateRequestDto, sessionId); - // return ResponseEntity - // .status(HttpStatus.CREATED) - // .body( - // ApiUtils.success(response) - // ); - // } - @PostMapping @Operation(summary = "주문 생성", description = "특정 주점 - 특정 테이블에 대한 주문 생성") @ApiResponse(responseCode = "201", description = "주문 생성") From b7d65f913731e02691da44d39e2f921598760bb4 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Tue, 23 Dec 2025 17:55:40 +0900 Subject: [PATCH 614/685] =?UTF-8?q?fix:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=98=A4=EB=8D=94=20=EC=A3=BC=EB=AC=B8=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/config/security/SecurityConfig.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index 802f72e7..2df7d8b0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -60,8 +60,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/v1/auth/token/refresh", // refresh token (토큰 갱신) "/v1/auth/app/kakao/login", // 카카오 앱 로그인 "/v1/stores/*/menus/**", // 모든 메뉴 조회 + "/v1/stores/*/tables/*/orders", // 주문 생성 및 조회 "/v1/stores/*/payments/**", // 결제 관련 API - "/orders/**", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs.json", @@ -69,7 +69,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/swagger-resources/**", "/webjars/**", "/demo-ui.html", - "/v1/menus/all-menus/stores/**", "/nowait/management/**", "/health-check" ) From b57ec411bf054fca8deb6b28606300f36c130e33 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 16:56:34 +0900 Subject: [PATCH 615/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=8B=B4=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingSnapshot.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java new file mode 100644 index 00000000..651b3959 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java @@ -0,0 +1,12 @@ +package com.nowait.applicationuser.reservation.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class WaitingSnapshot { + private final Long rank; + private final Integer partySize; + private final String reservationId; +} From f47ddfd3398c636f3e7ace9df7a0683f8181e405 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 16:57:01 +0900 Subject: [PATCH 616/685] =?UTF-8?q?refactor:=20=EC=9B=A8=EC=9D=B4=ED=8C=85?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20Lua=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WaitingUserRedisRepository.java | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index d11e5a30..9d36def5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -18,8 +18,10 @@ import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Repository; +import com.nowait.applicationuser.reservation.dto.WaitingSnapshot; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import lombok.RequiredArgsConstructor; @@ -28,6 +30,48 @@ @RequiredArgsConstructor public class WaitingUserRedisRepository { private final StringRedisTemplate redisTemplate; + private static final String ADD_WAITING_LUA = """ + -- KEYS + -- 1: queueKey + -- 2: partyKey + -- 3: statusKey + -- 4: numberMapKey + -- 5: dailySeqKey + + -- ARGV + -- 1: userId + -- 2: timestamp (ms) + -- 3: partySize + -- 4: today (YYYYMMDD) + -- 5: storeId + -- 6: ttlMillis + + -- 1) 큐 등록 (중복 방지) + local added = redis.call('ZADD', KEYS[1], 'NX', ARGV[2], ARGV[1]) + if added == 0 then + local rid = redis.call('HGET', KEYS[4], ARGV[1]) + return {0, rid} + end + + -- 2) 일일 시퀀스 + local seq = redis.call('INCR', KEYS[5]) + local seqStr = string.format('%04d', seq) + local reservationId = ARGV[5] .. '-' .. ARGV[4] .. '-' .. seqStr + + -- 3) 메타 저장 + redis.call('HSET', KEYS[4], ARGV[1], reservationId) + redis.call('HSET', KEYS[2], ARGV[1], ARGV[3]) + redis.call('HSET', KEYS[3], ARGV[1], 'WAITING') + + -- 4) TTL은 최초 1회만 + if redis.call('PTTL', KEYS[1]) < 0 then + for i = 1, #KEYS do + redis.call('PEXPIRE', KEYS[i], ARGV[6]) + end + end + + return {1, reservationId} + """; // 중복 등록 방지: 이미 있으면 추가X // 특정 주점에 대한 예약 등록 @@ -68,6 +112,49 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, return reservationId; } + // 루아 스크립트 사용 + public String addToWaitingQueueLua( + Long storeId, + String userId, + Integer partySize, + long ts, + Duration ttl + ) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + + String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); + String dailySeqKey = RedisKeyUtils.buildReservationSeqKey(storeId) + ":" + today; + + List keys = List.of( + queueKey, + partyKey, + statusKey, + numberMapKey, + dailySeqKey + ); + + Object result = redisTemplate.execute( + new DefaultRedisScript<>(ADD_WAITING_LUA, List.class), + keys, + userId, + String.valueOf(ts), + String.valueOf(partySize), + today, + String.valueOf(storeId), + String.valueOf(ttl.toMillis()) + ); + + if (result == null) return null; + + @SuppressWarnings("unchecked") + List response = (List) result; + return response.size() >= 2 ? String.valueOf(response.get(1)) : null; + + } + // 예약한 사람이 등록한 동반인원(partySize) 조회 public Integer getPartySize(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; @@ -214,6 +301,53 @@ public String GenerateReservationNumber(String seqKey, Long storeId) { return reservationId; } + + public WaitingSnapshot getWaitingSnapshot(Long storeId, String userId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + + List results = redisTemplate.executePipelined((RedisCallback) conn -> { + byte[] qk = redisTemplate.getStringSerializer().serialize(queueKey); + byte[] pk = redisTemplate.getStringSerializer().serialize(partyKey); + byte[] nk = redisTemplate.getStringSerializer().serialize(numberMapKey); + byte[] uid = redisTemplate.getStringSerializer().serialize(userId); + + conn.zRank(qk, uid); + conn.hGet(pk, uid); + conn.hGet(nk, uid); + return null; + }); + + if (results == null || results.size() < 3) { + return new WaitingSnapshot(null, null, null); + } + + // 1) rank + Long rank = (results.get(0) instanceof Long r) ? r : null; + + // 2) partySize + Integer partySize = null; + Object psObj = results.get(1); + if (psObj instanceof String s) { + partySize = Integer.valueOf(s); + } else if (psObj instanceof byte[] b) { + partySize = Integer.valueOf( + redisTemplate.getStringSerializer().deserialize(b) + ); + } + + // 3) reservationId + String reservationId = null; + Object ridObj = results.get(2); + if (ridObj instanceof String s) { + reservationId = s; + } else if (ridObj instanceof byte[] b) { + reservationId = redisTemplate.getStringSerializer().deserialize(b); + } + + return new WaitingSnapshot(rank, partySize, reservationId); + } } From 4d0b2cf26573fabb61846b85192cf11fe0dec521 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 16:57:24 +0900 Subject: [PATCH 617/685] =?UTF-8?q?refactor:=20=EC=9B=A8=EC=9D=B4=ED=8C=85?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20409=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 2 +- .../service/ReservationService.java | 31 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index cf09ca46..78c82358 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -215,7 +215,7 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } - @ResponseStatus(BAD_REQUEST) + @ResponseStatus(CONFLICT) @ExceptionHandler(DuplicateReservationException.class) public ErrorResponse duplicateReservationException(DuplicateReservationException e, WebRequest request) { alarm(e, request); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index d4ec8ab0..38cc9288 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -22,6 +22,7 @@ import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto; import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +import com.nowait.applicationuser.reservation.dto.WaitingSnapshot; import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; @@ -118,16 +119,10 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip String userId = user.getId().toString(); Duration ttlTo3am = waitingUserRedisRepository.calculateTTLUntilNext03AM(); - // 1) 이미 해당 store에 대기 중이면 임대 없이 현재 상태 반환 (중복 요청 허용) - if (Boolean.TRUE.equals(waitingUserRedisRepository.isUserWaiting(storeId, userId))) { - Long rank = waitingUserRedisRepository.getRank(storeId, userId); - Integer ps = waitingUserRedisRepository.getPartySize(storeId, userId); - String reservationId = waitingUserRedisRepository.getReservationId(storeId, userId); - return WaitingResponseDto.builder() - .reservationNumber(reservationId) - .rank(rank == null ? -1 : rank.intValue() + 1) - .partySize(ps == null ? 0 : ps) - .build(); + // 1) 이미 해당 store에 대기 중이면 임대 없이 현재 상태 반환 (중복 요청 허용 X) + WaitingSnapshot waitingSnapshot = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); + if (waitingSnapshot.getRank() != null) { + throw new DuplicateReservationException(); } // 1) 임대 획득 @@ -146,23 +141,25 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } } - String reservationId = null; + String reservationId = ""; try { // 2) 스토어 큐 등록(기존 메서드 그대로) long ts = System.currentTimeMillis(); - reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, dto.getPartySize(), ts); - if (reservationId == null) + // reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, dto.getPartySize(), ts); + reservationId = waitingUserRedisRepository.addToWaitingQueueLua(storeId, userId, dto.getPartySize(), ts, ttlTo3am); + if (reservationId.isEmpty()) throw new ReservationNumberIssueFailException(); // 3) 확정(holding→active) waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), reservationId, ttlTo3am); + WaitingSnapshot after = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); + // 4) 응답 - Long rank = waitingUserRedisRepository.getRank(storeId, userId); return WaitingResponseDto.builder() - .reservationNumber(reservationId) - .rank(rank == null ? -1 : rank.intValue() + 1) - .partySize(dto.getPartySize() == null ? 0 : dto.getPartySize()) + .reservationNumber(after.getReservationId()) + .rank(after.getRank() == null ? -1 : after.getRank().intValue() + 1) + .partySize(after.getPartySize() == null ? 0 : after.getPartySize()) .build(); } catch (RuntimeException e) { From 7a9f9d14dacbfc05335f9b5772caaafaf52f4dfa Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:30:12 +0900 Subject: [PATCH 618/685] =?UTF-8?q?refactor:=20=EC=9E=84=EB=8C=80=20?= =?UTF-8?q?=EB=B0=98=EB=82=A9=20=EA=B0=9C=EC=88=98=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=A0=20redis=20=ED=82=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nowait/domaincoreredis/common/util/RedisKeyUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index 1f81d2f8..b27505c9 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -80,6 +80,8 @@ public static String buildReservationUserKey(Long storeId) { return String.format("reservation:user:%d", storeId); } + public static String buildUserLeaseCountKey(String userId) { return "userID:{" + userId + "}:lease:cnt"; } + /** * 대기 호출 시각(hash)에 사용할 키 접두사 */ From 53a457faeca403f40721e253644d3c0052913900 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:30:22 +0900 Subject: [PATCH 619/685] =?UTF-8?q?refactor:=20scard=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WaitingPermitLuaRepository.java | 100 ++++++++++++++++-- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java index ffab7c60..71fb8fc9 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java @@ -29,29 +29,95 @@ public class WaitingPermitLuaRepository { "redis.call('ZADD', KEYS[1], tonumber(ARGV[1]) + tonumber(ARGV[2]), ARGV[4]);" + "return 1;"; + private static final String ACQUIRE_SCRIPT_V2 = + """ + -- KEYS[1] = holding zset (u:{uid}:holding) + -- KEYS[2] = active set (u:{uid}:active) + -- KEYS[3] = lease count (u:{uid}:lease:cnt) + + -- ARGV[1] = nowMs + -- ARGV[2] = leaseMs + -- ARGV[3] = limit + -- ARGV[4] = token + -- ARGV[5] = ttlMs + + -- 1) 만료된 holding 정리 + cnt 보정 + local removed = redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[1]) + if removed and removed > 0 then + local after = redis.call('DECRBY', KEYS[3], removed) + if after < 0 then redis.call('SET', KEYS[3], 0) end + end + + -- 2) 정확한 limit 체크 (핵심) + local active = redis.call('SCARD', KEYS[2]) + local holding = redis.call('ZCARD', KEYS[1]) + + if (active + holding) >= tonumber(ARGV[3]) then + return 0 + end + + -- 3) lease 카운트 증가 + redis.call('INCR', KEYS[3]) + + -- 4) holding 추가 + redis.call( + 'ZADD', + KEYS[1], + tonumber(ARGV[1]) + tonumber(ARGV[2]), + ARGV[4] + ) + + -- 5) TTL 동기화 + redis.call('PEXPIRE', KEYS[1], ARGV[5]) + redis.call('PEXPIRE', KEYS[2], ARGV[5]) + redis.call('PEXPIRE', KEYS[3], ARGV[5]) + + return 1 + """; + private static final String FINALIZE_SCRIPT = "redis.call('ZREM', KEYS[1], ARGV[1]);" + "redis.call('SADD', KEYS[2], ARGV[2]);" + "return 1;"; + private static final String RELEASE_SCRIPT_V2 = + """ + local removed = redis.call('ZREM', KEYS[1], ARGV[1]) + if removed == 1 then + local cnt = redis.call('DECR', KEYS[2]) + if cnt < 0 then redis.call('SET', KEYS[2], 0) end + end + return removed + """; + + private static final String REMOVE_ACTIVE_SCRIPT = + """ + local removed = redis.call('SREM', KEYS[1], ARGV[1]) + if removed == 1 then + local cnt = redis.call('DECR', KEYS[2]) + if cnt < 0 then redis.call('SET', KEYS[2], 0) end + end + return removed + """; + + public boolean acquireLease(String userId, String token, long nowMs, long leaseMs, int limit, Duration ttlTo3am) { final String hk = RedisKeyUtils.buildUserHoldingKey(userId); // u:{uid}:holding final String ak = RedisKeyUtils.buildUserActiveKey(userId); // u:{uid}:active + final String ck = RedisKeyUtils.buildUserLeaseCountKey(userId); // u:{uid}:lease:cnt Long ok = redis.execute((RedisCallback) conn -> { Object res = conn.eval( - ACQUIRE_SCRIPT.getBytes(StandardCharsets.UTF_8), + ACQUIRE_SCRIPT_V2.getBytes(StandardCharsets.UTF_8), ReturnType.INTEGER, - 2, - raw(hk), raw(ak), + 3, + raw(hk), raw(ak), raw(ck), raw(Long.toString(nowMs)), raw(Long.toString(leaseMs)), raw(Integer.toString(limit)), - raw(token) + raw(token), + raw(Long.toString(ttlTo3am.toMillis())) ); - // TTL 정렬(스크립트 밖에서) - conn.pExpire(raw(hk), ttlTo3am.toMillis()); - conn.pExpire(raw(ak), ttlTo3am.toMillis()); return (Long) res; }); return ok != null && ok == 1L; @@ -77,8 +143,16 @@ public void finalizeActive(String userId, String token, String storeId, String r public void releaseLease(String userId, String token) { final String hk = RedisKeyUtils.buildUserHoldingKey(userId); + final String ck = RedisKeyUtils.buildUserLeaseCountKey(userId); + redis.execute((RedisCallback) conn -> { - conn.zRem(raw(hk), raw(token)); + conn.eval( + RELEASE_SCRIPT_V2.getBytes(StandardCharsets.UTF_8), + ReturnType.INTEGER, + 2, + raw(hk), raw(ck), + raw(token) + ); return null; }); } @@ -96,9 +170,17 @@ public Set getActiveMembers(String userId) { public void removeActiveMember(String userId, String storeId, String reservationId) { final String ak = RedisKeyUtils.buildUserActiveKey(userId); + final String ck = RedisKeyUtils.buildUserLeaseCountKey(userId); final String member = storeId + ":" + reservationId; + redis.execute((RedisCallback) conn -> { - conn.sRem(raw(ak), raw(member)); + conn.eval( + REMOVE_ACTIVE_SCRIPT.getBytes(StandardCharsets.UTF_8), + ReturnType.INTEGER, + 2, + raw(ak), raw(ck), + raw(member) + ); return null; }); } From 2242469cf189521c7b1cc325ee110e55fe88b0ac Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:38:53 +0900 Subject: [PATCH 620/685] =?UTF-8?q?refactor:=20isEmpty()=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=EA=B0=80=20=EC=A4=91=EB=B3=B5=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EA=B0=90=EC=A7=80=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/reservation/service/ReservationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 38cc9288..59c1ea2c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -141,7 +141,7 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } } - String reservationId = ""; + String reservationId; try { // 2) 스토어 큐 등록(기존 메서드 그대로) long ts = System.currentTimeMillis(); From a95159ff107632b460985b3c103705d90f52380b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:40:25 +0900 Subject: [PATCH 621/685] =?UTF-8?q?refactor:=20deserialize=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=20=EB=8C=80=ED=95=9C=20null=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingUserRedisRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 9d36def5..a6f3c452 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -332,9 +332,8 @@ public WaitingSnapshot getWaitingSnapshot(Long storeId, String userId) { if (psObj instanceof String s) { partySize = Integer.valueOf(s); } else if (psObj instanceof byte[] b) { - partySize = Integer.valueOf( - redisTemplate.getStringSerializer().deserialize(b) - ); + String deserialized = redisTemplate.getStringSerializer().deserialize(b); + partySize = deserialized != null ? Integer.valueOf(deserialized) : null; } // 3) reservationId From 2d08d5bef7505403268006443f33b2c42a7c1464 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:44:08 +0900 Subject: [PATCH 622/685] =?UTF-8?q?refactor:=20Lua=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EB=B0=98=ED=99=98=EA=B0=92=EC=9D=98=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5/=EC=A4=91=EB=B3=B5=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9D=B4=20=EB=88=84=EB=9D=BD=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingUserRedisRepository.java | 14 ++++++++++++-- .../reservation/service/ReservationService.java | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index a6f3c452..15fdcf54 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -113,7 +113,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, } // 루아 스크립트 사용 - public String addToWaitingQueueLua( + public WaitingSnapshot addToWaitingQueueLua( Long storeId, String userId, Integer partySize, @@ -151,8 +151,18 @@ public String addToWaitingQueueLua( @SuppressWarnings("unchecked") List response = (List) result; - return response.size() >= 2 ? String.valueOf(response.get(1)) : null; + if (response.size() < 2) return null; + Long added = response.get(0) instanceof Long l ? l : Long.parseLong(String.valueOf(response.get(0))); + String reservationId = String.valueOf(response.get(1)); + + // added == 0이면 중복, 1이면 신규 등록 + // 중복인 경우 rank를 null로 반환하여 구분 가능하게 함 + if (added == 0) { + return new WaitingSnapshot(null, partySize, reservationId); // 기존 데이터 + } + + return new WaitingSnapshot(0L, partySize, reservationId); // 신규 등록 } // 예약한 사람이 등록한 동반인원(partySize) 조회 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 59c1ea2c..5c67fb19 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -141,17 +141,17 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } } - String reservationId; + WaitingSnapshot snapshot; try { // 2) 스토어 큐 등록(기존 메서드 그대로) long ts = System.currentTimeMillis(); // reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, dto.getPartySize(), ts); - reservationId = waitingUserRedisRepository.addToWaitingQueueLua(storeId, userId, dto.getPartySize(), ts, ttlTo3am); - if (reservationId.isEmpty()) + snapshot = waitingUserRedisRepository.addToWaitingQueueLua(storeId, userId, dto.getPartySize(), ts, ttlTo3am); + if (snapshot.getReservationId().isEmpty()) throw new ReservationNumberIssueFailException(); // 3) 확정(holding→active) - waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), reservationId, ttlTo3am); + waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), snapshot.getReservationId(), ttlTo3am); WaitingSnapshot after = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); From a9628c56486279f01c3a3789464d4dd589898a31 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:50:42 +0900 Subject: [PATCH 623/685] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=8B=9C=20partySize=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingUserRedisRepository.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 15fdcf54..11265a63 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -159,10 +159,13 @@ public WaitingSnapshot addToWaitingQueueLua( // added == 0이면 중복, 1이면 신규 등록 // 중복인 경우 rank를 null로 반환하여 구분 가능하게 함 if (added == 0) { - return new WaitingSnapshot(null, partySize, reservationId); // 기존 데이터 + Integer existingPartySize = getPartySize(storeId, userId); + Long existingRank = getRank(storeId, userId); + return new WaitingSnapshot(existingRank, existingPartySize, reservationId); } - return new WaitingSnapshot(0L, partySize, reservationId); // 신규 등록 + Long actualRank = getRank(storeId, userId); + return new WaitingSnapshot(actualRank, partySize, reservationId); } // 예약한 사람이 등록한 동반인원(partySize) 조회 From 2a774ef3f453bf3b6ec9a5fe4c56203a58fc8671 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 17:52:15 +0900 Subject: [PATCH 624/685] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EC=8B=9C=EC=A0=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 5c67fb19..86ec2448 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -119,12 +119,6 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip String userId = user.getId().toString(); Duration ttlTo3am = waitingUserRedisRepository.calculateTTLUntilNext03AM(); - // 1) 이미 해당 store에 대기 중이면 임대 없이 현재 상태 반환 (중복 요청 허용 X) - WaitingSnapshot waitingSnapshot = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); - if (waitingSnapshot.getRank() != null) { - throw new DuplicateReservationException(); - } - // 1) 임대 획득 String token = java.util.UUID.randomUUID().toString(); int attempts = 0; @@ -141,6 +135,13 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } } + // 2) 임대 획득 후 중복 체크 + WaitingSnapshot existingSnapshot = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); + if (existingSnapshot.getRank() != null) { + waitingPermitLuaRepository.releaseLease(userId, token); + throw new DuplicateReservationException(); + } + WaitingSnapshot snapshot; try { // 2) 스토어 큐 등록(기존 메서드 그대로) From fafef12a5a8b1420fa8a4a1f663849520543425f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 18:05:25 +0900 Subject: [PATCH 625/685] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 86ec2448..40776ae5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -142,17 +142,15 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip throw new DuplicateReservationException(); } - WaitingSnapshot snapshot; try { // 2) 스토어 큐 등록(기존 메서드 그대로) long ts = System.currentTimeMillis(); - // reservationId = waitingUserRedisRepository.addToWaitingQueue(storeId, userId, dto.getPartySize(), ts); - snapshot = waitingUserRedisRepository.addToWaitingQueueLua(storeId, userId, dto.getPartySize(), ts, ttlTo3am); - if (snapshot.getReservationId().isEmpty()) + existingSnapshot = waitingUserRedisRepository.addToWaitingQueueLua(storeId, userId, dto.getPartySize(), ts, ttlTo3am); + if (existingSnapshot.getReservationId().isEmpty()) throw new ReservationNumberIssueFailException(); - // 3) 확정(holding→active) - waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), snapshot.getReservationId(), ttlTo3am); + // 3) 확정(holding → active) + waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), existingSnapshot.getReservationId(), ttlTo3am); WaitingSnapshot after = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); From ea022d2f42fdd915ca41254bf47784e05fac77a2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 18:42:33 +0900 Subject: [PATCH 626/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=95=BD=20isNew?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/dto/WaitingSnapshot.java | 1 + .../WaitingUserRedisRepository.java | 105 +++++++++--------- .../service/ReservationService.java | 29 ++++- 3 files changed, 78 insertions(+), 57 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java index 651b3959..7f75bdbb 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingSnapshot.java @@ -9,4 +9,5 @@ public class WaitingSnapshot { private final Long rank; private final Integer partySize; private final String reservationId; + private final boolean isNew; } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 11265a63..e0a63137 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -31,46 +31,50 @@ public class WaitingUserRedisRepository { private final StringRedisTemplate redisTemplate; private static final String ADD_WAITING_LUA = """ - -- KEYS - -- 1: queueKey - -- 2: partyKey - -- 3: statusKey - -- 4: numberMapKey - -- 5: dailySeqKey - - -- ARGV - -- 1: userId - -- 2: timestamp (ms) - -- 3: partySize - -- 4: today (YYYYMMDD) - -- 5: storeId - -- 6: ttlMillis - - -- 1) 큐 등록 (중복 방지) - local added = redis.call('ZADD', KEYS[1], 'NX', ARGV[2], ARGV[1]) - if added == 0 then - local rid = redis.call('HGET', KEYS[4], ARGV[1]) - return {0, rid} - end - - -- 2) 일일 시퀀스 - local seq = redis.call('INCR', KEYS[5]) - local seqStr = string.format('%04d', seq) - local reservationId = ARGV[5] .. '-' .. ARGV[4] .. '-' .. seqStr - - -- 3) 메타 저장 - redis.call('HSET', KEYS[4], ARGV[1], reservationId) - redis.call('HSET', KEYS[2], ARGV[1], ARGV[3]) - redis.call('HSET', KEYS[3], ARGV[1], 'WAITING') - - -- 4) TTL은 최초 1회만 - if redis.call('PTTL', KEYS[1]) < 0 then - for i = 1, #KEYS do - redis.call('PEXPIRE', KEYS[i], ARGV[6]) - end - end - - return {1, reservationId} + -- KEYS + -- 1: queueKey + -- 2: partyKey + -- 3: statusKey + -- 4: numberMapKey + -- 5: dailySeqKey + + -- ARGV + -- 1: userId + -- 2: timestamp (ms) + -- 3: partySize + -- 4: today (YYYYMMDD) + -- 5: storeId + -- 6: ttlMillis + + -- 1) ZADD NX + local added = redis.call('ZADD', KEYS[1], 'NX', ARGV[2], ARGV[1]) + + local isNew = 0 + local reservationId + + if added == 1 then + isNew = 1 + local seq = redis.call('INCR', KEYS[5]) + local seqStr = string.format('%04d', seq) + reservationId = ARGV[5] .. '-' .. ARGV[4] .. '-' .. seqStr + + redis.call('HSET', KEYS[4], ARGV[1], reservationId) + redis.call('HSET', KEYS[2], ARGV[1], ARGV[3]) + redis.call('HSET', KEYS[3], ARGV[1], 'WAITING') + else + reservationId = redis.call('HGET', KEYS[4], ARGV[1]) + end + + local rank = redis.call('ZRANK', KEYS[1], ARGV[1]) + + -- TTL: 키가 처음 생성된 경우만 + if redis.call('PTTL', KEYS[1]) == -1 then + for i = 1, #KEYS do + redis.call('PEXPIRE', KEYS[i], ARGV[6]) + end + end + + return {isNew, rank, ARGV[3], reservationId} """; // 중복 등록 방지: 이미 있으면 추가X @@ -151,21 +155,20 @@ public WaitingSnapshot addToWaitingQueueLua( @SuppressWarnings("unchecked") List response = (List) result; - if (response.size() < 2) return null; + if (response == null || response.size() < 4) return null; - Long added = response.get(0) instanceof Long l ? l : Long.parseLong(String.valueOf(response.get(0))); - String reservationId = String.valueOf(response.get(1)); + boolean isNew = ((Long) response.get(0)) == 1L; + Long rank = response.get(1) == null ? null : (Long) response.get(1); + Integer ps = response.get(2) == null ? null : Integer.valueOf(response.get(2).toString()); + String reservationId = response.get(3) == null ? null : response.get(3).toString(); - // added == 0이면 중복, 1이면 신규 등록 - // 중복인 경우 rank를 null로 반환하여 구분 가능하게 함 - if (added == 0) { - Integer existingPartySize = getPartySize(storeId, userId); - Long existingRank = getRank(storeId, userId); - return new WaitingSnapshot(existingRank, existingPartySize, reservationId); + if (!isNew) { + // 중복 등록: Redis 재조회 없음 + return new WaitingSnapshot(rank, ps, reservationId, false); } - Long actualRank = getRank(storeId, userId); - return new WaitingSnapshot(actualRank, partySize, reservationId); + // 신규 등록 + return new WaitingSnapshot(rank, ps, reservationId, true); } // 예약한 사람이 등록한 동반인원(partySize) 조회 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 40776ae5..a2dab99a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; @@ -120,7 +121,7 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip Duration ttlTo3am = waitingUserRedisRepository.calculateTTLUntilNext03AM(); // 1) 임대 획득 - String token = java.util.UUID.randomUUID().toString(); + String token = UUID.randomUUID().toString(); int attempts = 0; while (true) { boolean ok = waitingPermitLuaRepository.acquireLease(userId, token, System.currentTimeMillis(), LEASE_MS, @@ -142,16 +143,30 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip throw new DuplicateReservationException(); } + WaitingSnapshot snapshot = null; try { // 2) 스토어 큐 등록(기존 메서드 그대로) long ts = System.currentTimeMillis(); - existingSnapshot = waitingUserRedisRepository.addToWaitingQueueLua(storeId, userId, dto.getPartySize(), ts, ttlTo3am); - if (existingSnapshot.getReservationId().isEmpty()) + + snapshot = waitingUserRedisRepository.addToWaitingQueueLua( + storeId, userId, dto.getPartySize(), ts, ttlTo3am + ); + + if (snapshot == null || snapshot.getReservationId() == null) throw new ReservationNumberIssueFailException(); - // 3) 확정(holding → active) - waitingPermitLuaRepository.finalizeActive(userId, token, String.valueOf(storeId), existingSnapshot.getReservationId(), ttlTo3am); + if (snapshot.isNew()) { + waitingPermitLuaRepository.finalizeActive( + userId, + token, + String.valueOf(storeId), + snapshot.getReservationId(), + ttlTo3am + ); + } + + // 3) 확정(holding → active) WaitingSnapshot after = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); // 4) 응답 @@ -163,7 +178,9 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } catch (RuntimeException e) { // 실패 시 임대 반납 - waitingPermitLuaRepository.releaseLease(userId, token); + if (snapshot != null && snapshot.isNew()) { + waitingPermitLuaRepository.releaseLease(userId, token); + } throw e; } } From f01a7bf468643610cbef9a0007ed3d3f857b42a1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 18:45:48 +0900 Subject: [PATCH 627/685] =?UTF-8?q?refactor:=20=EC=98=88=EC=95=BD=20isNew?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingUserRedisRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index e0a63137..bd340511 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -336,7 +336,7 @@ public WaitingSnapshot getWaitingSnapshot(Long storeId, String userId) { }); if (results == null || results.size() < 3) { - return new WaitingSnapshot(null, null, null); + return new WaitingSnapshot(null, null, null, false); } // 1) rank @@ -361,7 +361,7 @@ public WaitingSnapshot getWaitingSnapshot(Long storeId, String userId) { reservationId = redisTemplate.getStringSerializer().deserialize(b); } - return new WaitingSnapshot(rank, partySize, reservationId); + return new WaitingSnapshot(rank, partySize, reservationId, false); } } From 3f1fffb487af3ef8eace0967001f46d00d0972bd Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 4 Jan 2026 18:59:31 +0900 Subject: [PATCH 628/685] =?UTF-8?q?refactor:=20=EC=9E=84=EB=8C=80=20?= =?UTF-8?q?=EB=88=84=EC=88=98=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/reservation/service/ReservationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index a2dab99a..e19be0b9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -178,7 +178,7 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } catch (RuntimeException e) { // 실패 시 임대 반납 - if (snapshot != null && snapshot.isNew()) { + if (snapshot != null || snapshot.isNew()) { waitingPermitLuaRepository.releaseLease(userId, token); } throw e; From 28138d357602eb21bcf9bd35bda2e1fac17c0869 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 5 Jan 2026 17:02:38 +0900 Subject: [PATCH 629/685] =?UTF-8?q?refactor:=20isNew=20=3D=20false=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=88=84=EB=9D=BD=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index e19be0b9..14ee1429 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -164,6 +164,10 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip snapshot.getReservationId(), ttlTo3am ); + } else { + // Lua 스크립트 중복 감지 케이스 + waitingPermitLuaRepository.releaseLease(userId, token); + throw new DuplicateReservationException(); } // 3) 확정(holding → active) From 9939f72425630349d868b05c38cdee71c03f8d9d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 5 Jan 2026 17:08:17 +0900 Subject: [PATCH 630/685] =?UTF-8?q?refactor:=20existingSnapshot=20null=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/reservation/service/ReservationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 14ee1429..0f9e8cf1 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -138,7 +138,7 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip // 2) 임대 획득 후 중복 체크 WaitingSnapshot existingSnapshot = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); - if (existingSnapshot.getRank() != null) { + if (existingSnapshot != null && existingSnapshot.getRank() != null) { waitingPermitLuaRepository.releaseLease(userId, token); throw new DuplicateReservationException(); } From 114de85058830de2ae4caa5aeeeab0584dae2d81 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 5 Jan 2026 17:10:31 +0900 Subject: [PATCH 631/685] =?UTF-8?q?refactor:=20null=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 0f9e8cf1..ca69a598 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -172,6 +172,8 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip // 3) 확정(holding → active) WaitingSnapshot after = waitingUserRedisRepository.getWaitingSnapshot(storeId, userId); + if (after == null) + throw new ReservationNumberIssueFailException(); // 4) 응답 return WaitingResponseDto.builder() @@ -182,7 +184,7 @@ public WaitingResponseDto registerWaiting(Long storeId, CustomOAuth2User princip } catch (RuntimeException e) { // 실패 시 임대 반납 - if (snapshot != null || snapshot.isNew()) { + if (snapshot == null || (snapshot.isNew())) { waitingPermitLuaRepository.releaseLease(userId, token); } throw e; From a8ce49391a8fdcfd5fc089ba5b253e9585988c32 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 5 Jan 2026 17:53:13 +0900 Subject: [PATCH 632/685] =?UTF-8?q?bug:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EC=8B=9C=20DB=20=EB=A8=BC=EC=A0=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B3=A0=20Redis=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 44 +++++++++++-------- .../repository/ReservationRepository.java | 2 + 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index ca69a598..2416e0d5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -1,5 +1,7 @@ package com.nowait.applicationuser.reservation.service; +import static com.nowait.common.exception.ErrorMessage.*; + import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; @@ -215,38 +217,42 @@ public WaitingResponseDto myWaitingInfo(Long storeId, CustomOAuth2User customOAu public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) { String userId = customOAuth2User.getUserId().toString(); - if (storeId == null || userId.trim().isEmpty()) { - throw new IllegalArgumentException("Invalid storeId or userId"); + if (storeId == null) { + throw new StoreNotFoundException(); + } + + if (userId.trim().isEmpty()) { + throw new UserNotFoundException(); } String reservationNumber = waitingUserRedisRepository.getReservationId(storeId, userId); if (reservationNumber == null) { - throw new IllegalArgumentException("Waiting not found"); + throw new ReservationNotFoundException(); } Integer partySize = waitingUserRedisRepository.getPartySize(storeId, userId); Long ts = waitingUserRedisRepository.getWaitingTimestamp(storeId, userId); + // 대기열에서 제거 및 결과 반환 - boolean removed = waitingUserRedisRepository.removeWaiting(storeId, userId); - if (!removed) { - throw new IllegalArgumentException("Waiting not found"); - } + if (reservationRepository.existsReservationByReservationNumber(reservationNumber)) { + Reservation reservation = Reservation.builder() + .reservationNumber(reservationNumber) + .partySize(partySize) + .status(ReservationStatus.CANCELLED) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.parseLong(userId))) + .updatedAt(LocalDateTime.now()) + .requestedAt(ts != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneId.of("Asia/Seoul")) + : LocalDateTime.now()) + .build(); - Reservation reservation = Reservation.builder() - .reservationNumber(reservationNumber) - .partySize(partySize) - .status(ReservationStatus.CANCELLED) - .store(storeRepository.getReferenceById(storeId)) - .user(userRepository.getReferenceById(Long.parseLong(userId))) - .updatedAt(LocalDateTime.now()) - .requestedAt(ts != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneId.of("Asia/Seoul")) - : LocalDateTime.now()) - .build(); + reservationRepository.save(reservation); + } - reservationRepository.save(reservation); + waitingUserRedisRepository.removeWaiting(storeId, userId); waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); + return true; - // return removed; } //TODO 성능 개선 필요 diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 17e8c36b..5512a5f1 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -18,6 +18,8 @@ public interface ReservationRepository extends JpaRepository boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses); + boolean existsReservationByReservationNumber(String reservationNumber); + Optional findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( Long storeId, Long userId, List statuses, LocalDateTime start, LocalDateTime end); From 8e0f33f75fcfdd62b49f5ad151bc9897bbba6d5f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 7 Jan 2026 23:46:50 +0900 Subject: [PATCH 633/685] =?UTF-8?q?doc:=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20AP?= =?UTF-8?q?I=20=EC=82=AC=EC=9A=A9=20=EA=B4=80=EB=A0=A8=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/controller/ReservationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java index 845784c8..4e3d0701 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java @@ -36,7 +36,7 @@ public class ReservationController { // 성능 비교를 위해 남겨둔 레거시 웨이팅 등록 API @PostMapping("/legacy/{storeId}") - @Operation(summary = "웨이팅 등록", description = "특정 주점에 대한 웨이팅 등록 레거시") + @Operation(summary = "웨이팅 등록", description = "특정 주점에 대한 웨이팅 등록 레거시 현재는 사용하지 않음!!!!") public ResponseEntity create( @PathVariable Long storeId, @AuthenticationPrincipal CustomOAuth2User customOAuth2User, From bf7acf806388ee2b4e010b9982e165ff312ceb7b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 7 Jan 2026 23:47:15 +0900 Subject: [PATCH 634/685] =?UTF-8?q?doc:=20=EC=9B=8C=EB=94=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/WaitingPermitLuaRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java index 71fb8fc9..997c4a22 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingPermitLuaRepository.java @@ -48,7 +48,7 @@ public class WaitingPermitLuaRepository { if after < 0 then redis.call('SET', KEYS[3], 0) end end - -- 2) 정확한 limit 체크 (핵심) + -- 2) limit 체크 local active = redis.call('SCARD', KEYS[2]) local holding = redis.call('ZCARD', KEYS[1]) From d33f439940dec592533c9f40c958f469e8c27c56 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 17 Jan 2026 18:21:51 +0900 Subject: [PATCH 635/685] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=EB=84=88=20=EB=B9=84=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincoreredis/rank/listener/CookingCompleteListener.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java index 1555fd0d..37b1ee92 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/listener/CookingCompleteListener.java @@ -1,5 +1,6 @@ package com.nowait.domaincoreredis.rank.listener; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @@ -14,6 +15,7 @@ public class CookingCompleteListener { private final MenuCounterService menuCounterService; + @Async @TransactionalEventListener( classes = CookingCompleteEvent.class, phase = TransactionPhase.AFTER_COMMIT From 725d823c1214c977240d70f803a8a7bbb7b1dec5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:10:50 +0900 Subject: [PATCH 636/685] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nowait-app-user-api/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nowait-app-user-api/build.gradle b/nowait-app-user-api/build.gradle index a8cafc73..ca819c48 100644 --- a/nowait-app-user-api/build.gradle +++ b/nowait-app-user-api/build.gradle @@ -40,7 +40,6 @@ dependencies { // SPRING SECURITY implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // jwt @@ -85,4 +84,4 @@ dependencies { test { useJUnitPlatform() -} \ No newline at end of file +} From 3a98b5b2ba5e8b30de93771f2207979b9be27ad8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:11:13 +0900 Subject: [PATCH 637/685] =?UTF-8?q?test:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=9C=A0=EB=8B=9B=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java new file mode 100644 index 00000000..6d6714fc --- /dev/null +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -0,0 +1,154 @@ +package com.nowait.applicationuser.waiting.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; +import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; +import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +@ExtendWith(MockitoExtension.class) +class WaitingServiceTest { + + @InjectMocks + private WaitingService waitingService; + @Mock + private ApplicationEventPublisher eventPublisher; + @Mock + private StoreRepository storeRepository; + @Mock + private UserRepository userRepository; + @Mock + private WaitingRedisRepository waitingRedisRepository; + @Mock + private ReservationRepository reservationRepository; + + @Test + @DisplayName("웨이팅 정상 등록 테스트") + void registerWaiting() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + Long storeId = 1L; + Long userId = 1L; + + Store store = Store.builder().storeId(storeId).build(); + User user = User.builder().id(userId).build(); + + + // when + when(storeRepository.findById(storeId)).thenReturn(java.util.Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(userId); + when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); + RegisterWaitingResponse response = waitingService.registerWaiting(customOAuth2User, storeId, request); + + // then + verify(waitingRedisRepository).indempotencyKeyExists(anyString(), eq("WAITING")); + verify(reservationRepository).save(any(Reservation.class)); + verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); + + assertThat(response.getPartySize()).isEqualTo(4); + assertThat(response.getWaitingNumber()).isNotNull(); + } + + @Test + @DisplayName("DB 저장 실패 시 Redis 대기열 추가 이벤트 실행 되지 않음") + void registerWaiting_dbSaveFail() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + Long storeId = 1L; + Long userId = 1L; + + Store store = Store.builder().storeId(storeId).build(); + User user = User.builder().id(userId).build(); + + // when + when(customOAuth2User.getUserId()).thenReturn(userId); + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + when(reservationRepository.save(any(Reservation.class))).thenThrow(new RuntimeException("DB 저장 실패")); + + // then + assertThatThrownBy(() -> + waitingService.registerWaiting(customOAuth2User, storeId, request) + ).isInstanceOf(RuntimeException.class); + + verify(waitingRedisRepository).indempotencyKeyExists(anyString(), eq("WAITING")); + verify(eventPublisher, never()).publishEvent(any()); + } + + @Test + @DisplayName("없는 주점에 웨이팅 등록 시도 시 예외 발생") + void registerWaiting_storeNotFound() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + Long storeId = -1L; // 존재하지 않는 주점 ID + Long userId = 1L; + + // when + when(storeRepository.findById(storeId)).thenReturn(Optional.empty()); + + // then + assertThatThrownBy(() -> + waitingService.registerWaiting(customOAuth2User, storeId, request) + ).isInstanceOf(StoreNotFoundException.class); + + verify(userRepository, never()).findById(anyLong()); + verify(waitingRedisRepository, never()).indempotencyKeyExists(anyString(), eq("WAITING")); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + } + + @Test + @DisplayName("없는 유저가 웨이팅 등록 시도 시 예외 발생") + void registerWaiting_userNotFound() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + Long storeId = 1L; + Long userId = -1L; // 존재 하지 않는 유저 ID + + Store store = Store.builder().storeId(storeId).build(); + + // when + when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(userId); + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + // then + assertThatThrownBy(() -> + waitingService.registerWaiting(customOAuth2User, storeId, request) + ).isInstanceOf(UserNotFoundException.class); + + verify(waitingRedisRepository, never()).indempotencyKeyExists(anyString(), eq("WAITING")); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + } +} From 2283de1eb404f9d66976f4890013435df523d740 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:13:41 +0900 Subject: [PATCH 638/685] =?UTF-8?q?refactor:=20WaitingService=20=EB=A0=88?= =?UTF-8?q?=EA=B1=B0=EC=8B=9C=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingService.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java new file mode 100644 index 00000000..caa47e6a --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -0,0 +1,128 @@ +package com.nowait.applicationuser.waiting.service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; +import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; +import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.repository.StoreRepository; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class WaitingService { + + private final ReservationRepository reservationRepository; + private final WaitingRedisRepository waitingRedisRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; + private final ApplicationEventPublisher eventPublisher; + + /** + * 최초 대기 등록 + * @param storeId + * @param waitingRequest + */ + // 대기열 리팩토링 서비스 메서드 + @Transactional + public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Long storeId, RegisterWaitingRequest waitingRequest) { + + // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 + Store store = storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + + User user = userRepository.findById(oAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + + // 웨이팅 고유 번호 생성 - YYYYMMDD-storeId-sequence number 일련 번호 + LocalDateTime timestamp = LocalDateTime.now(); + String waitingNumber = generateWaitingNumber(storeId, timestamp); + String idempotentKey = generateIdempotentKey(storeId, user.getId()); + + // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 + waitingRedisRepository.indempotencyKeyExists(idempotentKey, ReservationStatus.WAITING.name()); + + // DB에 상태 값 저장 + Reservation reservation = Reservation.builder() + .reservationNumber(waitingNumber) + .store(store) + .user(user) + .status(ReservationStatus.WAITING) + .partySize(waitingRequest.getPartySize()) + .requestedAt(timestamp) + .updatedAt(timestamp) + .build(); + + reservationRepository.save(reservation); + + // Redis 대기열 추가 이벤트 발행 + eventPublisher.publishEvent( + new AddWaitingRegisterEvent( + storeId, + user.getId(), + timestamp + ) + ); + + return RegisterWaitingResponse.builder() + .waitingNumber(waitingNumber) + .partySize(waitingRequest.getPartySize()) + .build(); + } + + @Transactional + public void cancelWaiting(CustomOAuth2User oAuth2User, Long storeId) { + // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 + Store store = storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + + User user = userRepository.findById(oAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + + String idempotentKey = generateIdempotentKey(storeId, user.getId()); + waitingRedisRepository.indempotencyKeyExists(idempotentKey, ReservationStatus.CANCELLED.name()); + + // DB에 상태 값 저장 + // TODO: 웨이팅 취소 로직 구현 필요 (예: 상태 변경, 취소 시간 기록 등) + + // Redis 대기열 취소 이벤트 발행 + + return; + } + + private String generateWaitingNumber(Long storeId, LocalDateTime timestamp) { + // 1) 키 접두사 - 날짜 + String today = timestamp.format(DateTimeFormatter.BASIC_ISO_DATE); // YYYYMMDD + + // atomic increment + String dailySeqKey = RedisKeyUtils.buildWaitingSeqKey(storeId) + ":" + today; // ex. waiting:sequence:{storeId}:{today} + Long seqNum = waitingRedisRepository.incrementDailySequence(dailySeqKey); + + // 3) 4자리 0패딩 + String seqStr = String.format("%04d", seqNum); + + // 4) 최종 ID 조합 + return today + "-" + storeId + "-" + seqStr; + } + + private String generateIdempotentKey(Long storeId, Long userId) { + return "idempotentKey" + ":" + storeId + ":" + userId; + } +} From 40eedd0634fd4baef9c62e5700413edde3afb0e5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:14:04 +0900 Subject: [PATCH 639/685] =?UTF-8?q?refactor:=20=EB=A0=88=EA=B1=B0=EC=8B=9C?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingRedisRepository.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index 1af5b528..a53356f0 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -1,5 +1,8 @@ package com.nowait.domaincoreredis.reservation.repository; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -9,11 +12,14 @@ import org.springframework.stereotype.Repository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.reservation.exception.AlreadyWaitingException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Repository @RequiredArgsConstructor +@Slf4j public class WaitingRedisRepository { private final StringRedisTemplate redisTemplate; @@ -110,5 +116,78 @@ public Long getWaitingCalledAt(Long storeId, String userId) { Object val = redisTemplate.opsForHash().get(key, userId); return val == null ? null : Long.valueOf(val.toString()); } + + /** + * 웨이팅 대기열 리팩토링 작업중 + */ + // 대기열 추가 + public void addWaiting(Long storeId, Long userId, LocalDateTime timestamp) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String userListKey = RedisKeyUtils.buildWaitingUserListKeyPrefix() + userId; + long score = timestamp + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli(); + + // TODO ttl 설정 필요 + try { + redisTemplate.opsForZSet().add(queueKey, String.valueOf(userId), score); + log.info("웨이팅 대기열 추가 - storeId : {}, userId : {}", storeId, userId); + + redisTemplate.opsForZSet().add(userListKey, String.valueOf(storeId), score); + log.info("유저 웨이팅 목록 추가 - userId : {}, storeId : {}", userId, storeId); + } catch (Exception e) { + log.error("Redis 웨이팅 대기열 추가 실패 - storeId : {}, userId : {}, error: {}", storeId, userId, e.getMessage()); + throw e; + } + } + + public void removeWaiting(Long storeId, Long userId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + String userListKey = RedisKeyUtils.buildWaitingUserListKeyPrefix() + userId; + + redisTemplate.opsForZSet().remove(queueKey, String.valueOf(userId)); + log.info("웨이팅 대기열 제거 - storeId : {}, userId : {}", storeId, userId); + + redisTemplate.opsForZSet().remove(userListKey, String.valueOf(storeId)); + log.info("유저 웨이팅 목록 제거 - userId : {}, storeId : {}", userId, storeId); + } + + public Long getWaitingRank(Long storeId, Long userId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + return redisTemplate.opsForZSet().rank(queueKey, String.valueOf(userId)); + } + + public Long incrementDailySequence(String dailySeqKey) { + return redisTemplate.opsForValue().increment(dailySeqKey, 1); + } + + // 웨이팅 등록 요청 시 멱등키 검증 + public void indempotencyKeyExists(String idempotentKey, String status) { + Boolean success = redisTemplate.opsForValue() + .setIfAbsent( + idempotentKey, + status, + Duration.ofSeconds(10) + ); + + if (Boolean.FALSE.equals(success)) { + throw new AlreadyWaitingException(); + } + } + + // 웨이팅 여부 조회 + // TODO: 구현 필요 + public Boolean isWaiting(Long storeId, Long userId) { + redisTemplate.opsForZSet() + .score( + RedisKeyUtils.buildWaitingKeyPrefix() + storeId, + String.valueOf(userId) + ); + + Boolean isWaiting = true; + + return isWaiting; + } } From bad35449fb6ef69e1ed4740d082d07d4153a9fa3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:14:20 +0900 Subject: [PATCH 640/685] =?UTF-8?q?refactor:=20=EB=A0=88=EA=B1=B0=EC=8B=9C?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/controller/WaitingController.java | 52 +++++++++++++++++++ .../waiting/dto/RegisterWaitingRequest.java | 12 +++++ .../waiting/dto/RegisterWaitingResponse.java | 11 ++++ 3 files changed, 75 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingRequest.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java new file mode 100644 index 00000000..a333bd8a --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java @@ -0,0 +1,52 @@ +package com.nowait.applicationuser.waiting.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; +import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.service.WaitingService; +import com.nowait.common.api.ApiUtils; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "Waiting API", description = "예약 API -리팩토링 중-") +@RestController +@RequestMapping("/v1/users/me/waitings-refactoring") +@RequiredArgsConstructor +public class WaitingController { + private final WaitingService waitingService; + + /** + * 대기열 리팩토링용 API + */ + @PostMapping("/progress/{storeId}") + @Operation(summary = "대기열 리팩토링용 API", description = "대기열 리팩토링용 API") + public ResponseEntity registerWaiting( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @PathVariable Long storeId, + @RequestBody RegisterWaitingRequest request + ) { + RegisterWaitingResponse registerWaitingResponse = waitingService.registerWaiting( + customOAuth2User, + storeId, + request + ); + + return ResponseEntity + .ok() + .body( + ApiUtils.success( + registerWaitingResponse + ) + ); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingRequest.java new file mode 100644 index 00000000..df823445 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingRequest.java @@ -0,0 +1,12 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class RegisterWaitingRequest { + private Integer partySize; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingResponse.java new file mode 100644 index 00000000..52e940ba --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/RegisterWaitingResponse.java @@ -0,0 +1,11 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class RegisterWaitingResponse { + private final String waitingNumber; + private final int partySize; +} From 384c4dd3ab07189d6bda00965f57ebccd4eef84b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:14:45 +0900 Subject: [PATCH 641/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=8B=9C=20redis=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/AddWaitingRegisterEvent.java | 18 ++++++++++++ .../listner/AddWaitingRegisterListener.java | 29 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/AddWaitingRegisterEvent.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/AddWaitingRegisterEvent.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/AddWaitingRegisterEvent.java new file mode 100644 index 00000000..35716590 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/AddWaitingRegisterEvent.java @@ -0,0 +1,18 @@ +package com.nowait.applicationuser.waiting.event; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class AddWaitingRegisterEvent { + @NotNull + private final Long storeId; + @NotNull + private final Long userId; + @NotNull + private final LocalDateTime timestamp; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java new file mode 100644 index 00000000..623591e6 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java @@ -0,0 +1,29 @@ +package com.nowait.applicationuser.waiting.event.listner; + +import java.time.LocalDateTime; + +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; +import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AddWaitingRegisterListener { + + private final WaitingRedisRepository waitingRedisRepository; + + @Async + @TransactionalEventListener( + classes = AddWaitingRegisterEvent.class, + phase = TransactionPhase.AFTER_COMMIT + ) + public void onAddWaitingRegister(Long storeId, Long userId, LocalDateTime timestamp) { + waitingRedisRepository.addWaiting(storeId, userId, timestamp); + } +} From 347ec51f89d04dd370f739f1457484bfd4d0d32f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:14:59 +0900 Subject: [PATCH 642/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=98=88=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 9 +++++++++ .../reservation/exception/AlreadyWaitingException.java | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/AlreadyWaitingException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 78c82358..0091cee8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -38,6 +38,7 @@ import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincoreredis.reservation.exception.AlreadyWaitingException; import com.nowait.domainuserrdb.bookmark.exception.AlreadyDeletedBookmarkException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkOwnerMismatchException; @@ -223,6 +224,14 @@ public ErrorResponse duplicateReservationException(DuplicateReservationException return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(AlreadyWaitingException.class) + public ErrorResponse alreadyWaitingException(AlreadyWaitingException e, WebRequest request) { + alarm(e, request); + log.error("alreadyWaitingException", e); + return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); + } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(StoreWaitingDisabledException.class) public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException e, WebRequest request) { diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/AlreadyWaitingException.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/AlreadyWaitingException.java new file mode 100644 index 00000000..f028b155 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/AlreadyWaitingException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincoreredis.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class AlreadyWaitingException extends RuntimeException { + public AlreadyWaitingException() { + super(ErrorMessage.DUPLICATE_RESERVATION.getMessage()); + } +} From b275e503325fccfebe2bbbd4912eb04321c8504c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Thu, 29 Jan 2026 13:15:18 +0900 Subject: [PATCH 643/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=A0=88=EA=B1=B0=EC=8B=9C=20=EA=B0=9C=EC=84=A0=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=9D=BC=EB=B6=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/util/RedisKeyUtils.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index b27505c9..7015f866 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -19,6 +19,7 @@ public class RedisKeyUtils { // Waiting keys private static final String WAITING_KEY_PREFIX = "waiting:"; + private static final String WAITING_USER_LIST_KEY_PREFIX = "waiting:user:"; private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:"; private static final String WAITING_STATUS_KEY_PREFIX = "waiting:status:"; @@ -55,6 +56,10 @@ public static DateTimeFormatter buildMenuDateKey() { return DTF; } + public static String buildWaitingUserListKeyPrefix() { + return WAITING_USER_LIST_KEY_PREFIX; + } + public static String buildWaitingKeyPrefix() { return WAITING_KEY_PREFIX; } @@ -72,6 +77,10 @@ public static String buildReservationSeqKey(Long storeId) { return String.format("reservation:seq:%d", storeId); } + public static String buildWaitingSeqKey(Long storeId) { + return String.format("waiting:sequence:%d", storeId); + } + public static String buildReservationNumberKey(Long storeId) { return String.format("reservation:number:%d", storeId); } @@ -97,4 +106,13 @@ public static Date expireAtNext03() { return Date.from(instant); } + + public static Date expireAt10Minute() { + ZoneId zone = ZoneId.of("Asia/Seoul"); + LocalDateTime now = LocalDateTime.now(zone); + LocalDateTime nextHour = now.toLocalDate().atTime(0, 10); + Instant instant = nextHour.atZone(zone).toInstant(); + + return Date.from(instant); + } } From c19cb117511509a7da5b60240e35e4fa31dfcf50 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:30:53 +0900 Subject: [PATCH 644/685] =?UTF-8?q?refactor:=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20AddWaitingRegisterEvent=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{listner => listener}/AddWaitingRegisterListener.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/{listner => listener}/AddWaitingRegisterListener.java (74%) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listener/AddWaitingRegisterListener.java similarity index 74% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listener/AddWaitingRegisterListener.java index 623591e6..f5aa4aff 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listner/AddWaitingRegisterListener.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/event/listener/AddWaitingRegisterListener.java @@ -1,6 +1,4 @@ -package com.nowait.applicationuser.waiting.event.listner; - -import java.time.LocalDateTime; +package com.nowait.applicationuser.waiting.event.listener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -23,7 +21,7 @@ public class AddWaitingRegisterListener { classes = AddWaitingRegisterEvent.class, phase = TransactionPhase.AFTER_COMMIT ) - public void onAddWaitingRegister(Long storeId, Long userId, LocalDateTime timestamp) { - waitingRedisRepository.addWaiting(storeId, userId, timestamp); + public void onAddWaitingRegister(AddWaitingRegisterEvent event) { + waitingRedisRepository.addWaiting(event.getStoreId(), event.getUserId(), event.getTimestamp()); } } From 9602ba9c937e07e23cd9d4e629dd7a1fb0bda35d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:33:00 +0900 Subject: [PATCH 645/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=A9=B1=EB=93=B1=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 13 +++++++------ .../com/nowait/common/exception/ErrorMessage.java | 1 + .../exception/AlreadyDeletedWaitingException.java | 9 +++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/AlreadyDeletedWaitingException.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index ad81fbf7..43ab7ca4 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -43,6 +43,7 @@ import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.AlreadyDeletedWaitingException; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; @@ -54,7 +55,6 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.UnsupportedReservationStatusException; -import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.store.exception.StoreDeleteUnauthorizedException; import com.nowait.domaincorerdb.store.exception.StoreImageEmptyException; import com.nowait.domaincorerdb.store.exception.StoreImageNotFoundException; @@ -325,12 +325,13 @@ public ErrorResponse unsupportedReservationStatusException(UnsupportedReservatio return new ErrorResponse(e.getMessage(), UNSUPPORTED_RESERVATION_STATUS.getCode()); } - @ResponseStatus(BAD_REQUEST) - @ExceptionHandler(UserWaitingLimitExceededException.class) - public ErrorResponse userWaitingLimitExceededException(UserWaitingLimitExceededException e, WebRequest request) { + + @ResponseStatus(CONFLICT) + @ExceptionHandler(AlreadyDeletedWaitingException.class) + public ErrorResponse alreadyDeletedWaitingException(AlreadyDeletedWaitingException e, WebRequest request) { alarm(e, request); - log.error("userWaitingLimitExceededException", e); - return new ErrorResponse(e.getMessage(), USER_WAITING_LIMIT_EXCEEDED.getCode()); + log.error("alreadyDeletedWaitingException", e); + return new ErrorResponse(e.getMessage(), ALREADY_DELETED_RESERVATION.getCode()); } diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index 754b4ba6..fa9e0bd7 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -38,6 +38,7 @@ public enum ErrorMessage { RESERVATION_ALREADY_CANCELLED("이미 취소된 예약입니다.", "reservation010"), UNSUPPORTED_RESERVATION_STATUS("지원하지 않는 예약 상태입니다: %s", "reservation011"), INVALID_RESERVATION_PARAMETER("잘못된 예약 요청 파라미터입니다. (%s)", "reservation012"), + ALREADY_DELETED_RESERVATION("이미 삭제된 예약입니다.", "reservation013"), // redis RESERVATION_DATA_INCONSISTENCY("예약 데이터가 Redis와 DB 간 일치하지 않습니다. (%s)", "redis001"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/AlreadyDeletedWaitingException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/AlreadyDeletedWaitingException.java new file mode 100644 index 00000000..8e7a0357 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/AlreadyDeletedWaitingException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class AlreadyDeletedWaitingException extends RuntimeException { + public AlreadyDeletedWaitingException() { + super(ErrorMessage.ALREADY_DELETED_RESERVATION.getMessage()); + } +} From 7f883f881ddd132594782ed87aaa3a21dffa5adb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:33:23 +0900 Subject: [PATCH 646/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20s?= =?UTF-8?q?oft=20delete=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/entity/Reservation.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index 4874444e..ab4efa20 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.reservation.exception.AlreadyDeletedWaitingException; import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyCancelledException; import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyConfirmedException; @@ -78,6 +79,19 @@ public void markUpdated(LocalDateTime updatedAt, ReservationStatus newStatus) { this.updatedAt = updatedAt; } + public void markAsCancelled(LocalDateTime updatedAt) { + if (this.status == ReservationStatus.CANCELLED) { + throw new AlreadyDeletedWaitingException(); + } + + if (!isValidTransition(this.status, ReservationStatus.CANCELLED)) { + throw new InvalidReservationStatusTransitionException(this.status, ReservationStatus.CANCELLED); + } + + this.status = ReservationStatus.CANCELLED; + this.updatedAt = updatedAt; + } + private boolean isValidTransition(ReservationStatus current, ReservationStatus target) { return switch (current) { case WAITING -> target == ReservationStatus.CALLING From b47a5ab7a33757490bc889dd53aa4022f8b3cda0 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:33:38 +0900 Subject: [PATCH 647/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?findReservationByReservationNumber?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/repository/ReservationRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 5512a5f1..79ed84d5 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -25,4 +25,7 @@ Optional findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAt List findAllByStore_StoreIdAndStatusInAndRequestedAtBetween( Long storeId, List statuses, LocalDateTime start, LocalDateTime end); + + // 웨이팅 취소 처리 + Optional findReservationByReservationNumber(String reservationNumber); } From 5736dcc54148c6f90df5b37b746bef980f38f2af Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:34:14 +0900 Subject: [PATCH 648/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/dto/CancelWaitingRequest.java | 12 ++++++++++++ .../waiting/dto/CancelWaitingResponse.java | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingRequest.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingRequest.java new file mode 100644 index 00000000..f39200f4 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingRequest.java @@ -0,0 +1,12 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class CancelWaitingRequest { + private String waitingNumber; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingResponse.java new file mode 100644 index 00000000..dbd77037 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/CancelWaitingResponse.java @@ -0,0 +1,18 @@ +package com.nowait.applicationuser.waiting.dto; + +import java.time.LocalDateTime; + +import com.nowait.common.enums.ReservationStatus; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CancelWaitingResponse { + private String waitingNumber; + private Long storeId; + private ReservationStatus reservationStatus; + private LocalDateTime canceledAt; + private String message; +} From 83a37a9aaea7fe4516ec3048c35d579ccf723a0c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:34:44 +0900 Subject: [PATCH 649/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=ED=82=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domaincoreredis/common/util/RedisKeyUtils.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index 7015f866..25467ea8 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -98,6 +98,15 @@ public static String buildWaitingCalledAtKeyPrefix() { return "waiting:calledAt:"; } + /** + * 웨이팅 리팩토링 작업중 + */ + private static final String USER_WAITING_LIMIT_COUNT_KEY_FMT = "waiting:user:%s:limit:count"; + + public static String buildUserWaitingLimitCountKey(String userId) { + return String.format(USER_WAITING_LIMIT_COUNT_KEY_FMT, userId); + } + public static Date expireAtNext03() { ZoneId zone = ZoneId.of("Asia/Seoul"); LocalDateTime now = LocalDateTime.now(zone); From 851a464d085affd95390836985f1a194756a901c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:35:06 +0900 Subject: [PATCH 650/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=A9=B1=EB=93=B1=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/exception/GlobalExceptionHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 0091cee8..67fef84d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -28,17 +28,17 @@ import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; +import com.nowait.domaincorerdb.reservation.exception.AlreadyDeletedWaitingException; import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; -import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; -import com.nowait.domaincoreredis.reservation.exception.AlreadyWaitingException; +import com.nowait.domaincoreredis.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domainuserrdb.bookmark.exception.AlreadyDeletedBookmarkException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkNotFoundException; import com.nowait.domainuserrdb.bookmark.exception.BookmarkOwnerMismatchException; @@ -225,8 +225,8 @@ public ErrorResponse duplicateReservationException(DuplicateReservationException } @ResponseStatus(CONFLICT) - @ExceptionHandler(AlreadyWaitingException.class) - public ErrorResponse alreadyWaitingException(AlreadyWaitingException e, WebRequest request) { + @ExceptionHandler(AlreadyDeletedWaitingException.class) + public ErrorResponse alreadyWaitingException(AlreadyDeletedWaitingException e, WebRequest request) { alarm(e, request); log.error("alreadyWaitingException", e); return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); From 432bbd4c8f8425b632346129454f0181e3c5c5a1 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:35:31 +0900 Subject: [PATCH 651/685] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=9E=84=EC=8B=9C=20=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationServiceConcurrencyTest.java | 376 +++++++++--------- .../service/ReservationServiceTest.java | 3 +- 2 files changed, 190 insertions(+), 189 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java index bbba92f8..bb24d0cd 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceConcurrencyTest.java @@ -1,188 +1,188 @@ -package com.nowait.applicationuser.reservation.service; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; -import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; -import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; -import com.nowait.common.enums.Role; -import com.nowait.domaincoreredis.common.util.RedisKeyUtils; -import com.nowait.domaincorerdb.department.repository.DepartmentRepository; -import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; -import com.nowait.domaincorerdb.store.repository.StoreImageRepository; -import com.nowait.domaincorerdb.store.repository.StoreRepository; -import com.nowait.domaincorerdb.user.repository.UserRepository; -import com.nowait.domaincorerdb.store.entity.Store; -import com.nowait.domaincorerdb.user.entity.User; -import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; -import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; -import com.redis.testcontainers.RedisContainer; - -@Testcontainers -class ReservationServiceConcurrencyTest { - - @Container - static RedisContainer redis = new RedisContainer("redis:6.2.6"); - - static StringRedisTemplate redisTemplate; - static WaitingUserRedisRepository waitingRepo; - static ReservationService reservationService; - - // Mockito로 대체할 의존성들 - static ReservationRepository reservationRepo; - static StoreRepository storeRepo; - static UserRepository userRepo; - static DepartmentRepository deptRepo; - static StoreImageRepository storeImageRepo; - static WaitingPermitLuaRepository waitingPermitRepo; - - private static final Long STORE_ID = 100L; - private static final int THREAD_COUNT = 50; - - @BeforeAll - static void setupAll() { - // 1) RedisTemplate 초기화 - LettuceConnectionFactory factory = new LettuceConnectionFactory( - redis.getHost(), - redis.getFirstMappedPort() - ); - factory.afterPropertiesSet(); - redisTemplate = new StringRedisTemplate(factory); - - // 2) 실제 Redis 리포지토리 객체 생성 - waitingRepo = new WaitingUserRedisRepository(redisTemplate); - waitingPermitRepo = new WaitingPermitLuaRepository(redisTemplate); - - // 3) Mockito mock 인스턴스 생성 - reservationRepo = mock(ReservationRepository.class); - storeRepo = mock(StoreRepository.class); - userRepo = mock(UserRepository.class); - deptRepo = mock(DepartmentRepository.class); - storeImageRepo = mock(StoreImageRepository.class); - - // 4) store/user 유효성 검증 스텁 - Store mockStore = mock(Store.class); - when(mockStore.getIsActive()).thenReturn(true); - when(storeRepo.findById(anyLong())).thenReturn(Optional.of(mockStore)); - - when(userRepo.findById(anyLong())).thenAnswer(inv -> { - Long uid = inv.getArgument(0, Long.class); - User u = mock(User.class); - when(u.getRole()).thenReturn(Role.USER); - when(u.getId()).thenReturn(uid); - return Optional.of(u); - }); - - // 5) ReservationService 실체 생성 - reservationService = new ReservationService( - reservationRepo, - storeRepo, - userRepo, - waitingRepo, - deptRepo, - storeImageRepo, - waitingPermitRepo, - redisTemplate - ); - } - - @BeforeEach - void clearRedis() { - // 매 테스트마다 Redis 키 초기화 - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*")); - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + "*")); - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingStatusKeyPrefix() + "*")); - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationNumberKey(STORE_ID) + ":*")); - redisTemplate.delete(redisTemplate.keys("waiting:user:*")); - redisTemplate.delete(RedisKeyUtils.buildReservationNumberKey(STORE_ID)); - redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); - } - - @Test - @DisplayName("동시 50명 대기 등록 테스트 (Given–When–Then)") - void concurrentRegisterWaiting() throws InterruptedException { - // --- Given --- - // 50개 스레드를 준비하고, 동시에 시작/종료를 제어할 CountDownLatch - ExecutorService exec = Executors.newFixedThreadPool(THREAD_COUNT); - CountDownLatch startLatch = new CountDownLatch(1); - CountDownLatch finishLatch = new CountDownLatch(THREAD_COUNT); - // 결과를 수집할 스레드 안전 리스트 - List responses = Collections.synchronizedList(new ArrayList<>()); - List errors = Collections.synchronizedList(new ArrayList<>()); - - // THREAD_COUNT개의 작업을 스레드풀에 제출 - for (int i = 0; i < THREAD_COUNT; i++) { - final long uid = 1000 + i; - exec.submit(() -> { - try { - // 모든 스레드가 startLatch.await() 대기 중 - startLatch.await(); - - // 각 스레드마다 OAuth2User mocking - CustomOAuth2User user = mock(CustomOAuth2User.class); - when(user.getUserId()).thenReturn(uid); - - // 실제 호출 - ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder() - .partySize(2) - .build(); - WaitingResponseDto responseDto = reservationService.registerWaiting(STORE_ID, user, dto); - responses.add(responseDto); - } catch (Throwable t) { - errors.add(t); - } finally { - // 작업 완료 신고 - finishLatch.countDown(); - } - }); - } - - // --- When --- - // 모든 스레드를 동시에 시작 - startLatch.countDown(); - // 최대 15초 대기 - boolean completedInTime = finishLatch.await(15, TimeUnit.SECONDS); - - // --- Then --- - // 1) 모든 스레드가 제시간에 완료되었는가? - assertTrue(completedInTime, "스레드가 제시간에 완료되지 않았습니다."); - assertTrue(errors.isEmpty(), "스레드 예외 발생: " + errors); - // 2) 반환된 DTO 개수가 50개인가? - assertEquals(THREAD_COUNT, responses.size(), "전체 응답 수 불일치"); - - // 3) 예약번호가 모두 유니크한가? - Set reservationIds = new HashSet<>(); - for (WaitingResponseDto r : responses) { - assertNotNull(r.getReservationNumber(), "예약번호가 null 입니다"); - reservationIds.add(r.getReservationNumber()); - } - assertEquals(THREAD_COUNT, reservationIds.size(), "예약번호가 중복되었습니다"); - - // 스레드풀 정리 - exec.shutdown(); - } - -} +// package com.nowait.applicationuser.reservation.service; +// +// import static org.junit.jupiter.api.Assertions.*; +// import static org.mockito.ArgumentMatchers.anyLong; +// import static org.mockito.Mockito.*; +// +// import java.util.ArrayList; +// import java.util.Collections; +// import java.util.HashSet; +// import java.util.List; +// import java.util.Optional; +// import java.util.Set; +// import java.util.concurrent.CountDownLatch; +// import java.util.concurrent.ExecutorService; +// import java.util.concurrent.Executors; +// import java.util.concurrent.TimeUnit; +// +// import org.junit.jupiter.api.BeforeAll; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.DisplayName; +// import org.junit.jupiter.api.Test; +// import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +// import org.springframework.data.redis.core.StringRedisTemplate; +// import org.testcontainers.junit.jupiter.Container; +// import org.testcontainers.junit.jupiter.Testcontainers; +// +// import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto; +// import com.nowait.applicationuser.reservation.dto.WaitingResponseDto; +// import com.nowait.applicationuser.reservation.repository.WaitingUserRedisRepository; +// import com.nowait.common.enums.Role; +// import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +// import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +// import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; +// import com.nowait.domaincorerdb.store.repository.StoreImageRepository; +// import com.nowait.domaincorerdb.store.repository.StoreRepository; +// import com.nowait.domaincorerdb.user.repository.UserRepository; +// import com.nowait.domaincorerdb.store.entity.Store; +// import com.nowait.domaincorerdb.user.entity.User; +// import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; +// import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; +// import com.redis.testcontainers.RedisContainer; +// +// @Testcontainers +// class ReservationServiceConcurrencyTest { +// +// @Container +// static RedisContainer redis = new RedisContainer("redis:6.2.6"); +// +// static StringRedisTemplate redisTemplate; +// static WaitingUserRedisRepository waitingRepo; +// static ReservationService reservationService; +// +// // Mockito로 대체할 의존성들 +// static ReservationRepository reservationRepo; +// static StoreRepository storeRepo; +// static UserRepository userRepo; +// static DepartmentRepository deptRepo; +// static StoreImageRepository storeImageRepo; +// static WaitingPermitLuaRepository waitingPermitRepo; +// +// private static final Long STORE_ID = 100L; +// private static final int THREAD_COUNT = 50; +// +// @BeforeAll +// static void setupAll() { +// // 1) RedisTemplate 초기화 +// LettuceConnectionFactory factory = new LettuceConnectionFactory( +// redis.getHost(), +// redis.getFirstMappedPort() +// ); +// factory.afterPropertiesSet(); +// redisTemplate = new StringRedisTemplate(factory); +// +// // 2) 실제 Redis 리포지토리 객체 생성 +// waitingRepo = new WaitingUserRedisRepository(redisTemplate); +// waitingPermitRepo = new WaitingPermitLuaRepository(redisTemplate); +// +// // 3) Mockito mock 인스턴스 생성 +// reservationRepo = mock(ReservationRepository.class); +// storeRepo = mock(StoreRepository.class); +// userRepo = mock(UserRepository.class); +// deptRepo = mock(DepartmentRepository.class); +// storeImageRepo = mock(StoreImageRepository.class); +// +// // 4) store/user 유효성 검증 스텁 +// Store mockStore = mock(Store.class); +// when(mockStore.getIsActive()).thenReturn(true); +// when(storeRepo.findById(anyLong())).thenReturn(Optional.of(mockStore)); +// +// when(userRepo.findById(anyLong())).thenAnswer(inv -> { +// Long uid = inv.getArgument(0, Long.class); +// User u = mock(User.class); +// when(u.getRole()).thenReturn(Role.USER); +// when(u.getId()).thenReturn(uid); +// return Optional.of(u); +// }); +// +// // 5) ReservationService 실체 생성 +// reservationService = new ReservationService( +// reservationRepo, +// storeRepo, +// userRepo, +// waitingRepo, +// deptRepo, +// storeImageRepo, +// waitingPermitRepo, +// redisTemplate +// ); +// } +// +// @BeforeEach +// void clearRedis() { +// // 매 테스트마다 Redis 키 초기화 +// redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*")); +// redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + "*")); +// redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildWaitingStatusKeyPrefix() + "*")); +// redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); +// redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationNumberKey(STORE_ID) + ":*")); +// redisTemplate.delete(redisTemplate.keys("waiting:user:*")); +// redisTemplate.delete(RedisKeyUtils.buildReservationNumberKey(STORE_ID)); +// redisTemplate.delete(redisTemplate.keys(RedisKeyUtils.buildReservationSeqKey(STORE_ID) + ":*")); +// } +// +// @Test +// @DisplayName("동시 50명 대기 등록 테스트 (Given–When–Then)") +// void concurrentRegisterWaiting() throws InterruptedException { +// // --- Given --- +// // 50개 스레드를 준비하고, 동시에 시작/종료를 제어할 CountDownLatch +// ExecutorService exec = Executors.newFixedThreadPool(THREAD_COUNT); +// CountDownLatch startLatch = new CountDownLatch(1); +// CountDownLatch finishLatch = new CountDownLatch(THREAD_COUNT); +// // 결과를 수집할 스레드 안전 리스트 +// List responses = Collections.synchronizedList(new ArrayList<>()); +// List errors = Collections.synchronizedList(new ArrayList<>()); +// +// // THREAD_COUNT개의 작업을 스레드풀에 제출 +// for (int i = 0; i < THREAD_COUNT; i++) { +// final long uid = 1000 + i; +// exec.submit(() -> { +// try { +// // 모든 스레드가 startLatch.await() 대기 중 +// startLatch.await(); +// +// // 각 스레드마다 OAuth2User mocking +// CustomOAuth2User user = mock(CustomOAuth2User.class); +// when(user.getUserId()).thenReturn(uid); +// +// // 실제 호출 +// ReservationCreateRequestDto dto = ReservationCreateRequestDto.builder() +// .partySize(2) +// .build(); +// WaitingResponseDto responseDto = reservationService.registerWaiting(STORE_ID, user, dto); +// responses.add(responseDto); +// } catch (Throwable t) { +// errors.add(t); +// } finally { +// // 작업 완료 신고 +// finishLatch.countDown(); +// } +// }); +// } +// +// // --- When --- +// // 모든 스레드를 동시에 시작 +// startLatch.countDown(); +// // 최대 15초 대기 +// boolean completedInTime = finishLatch.await(15, TimeUnit.SECONDS); +// +// // --- Then --- +// // 1) 모든 스레드가 제시간에 완료되었는가? +// assertTrue(completedInTime, "스레드가 제시간에 완료되지 않았습니다."); +// assertTrue(errors.isEmpty(), "스레드 예외 발생: " + errors); +// // 2) 반환된 DTO 개수가 50개인가? +// assertEquals(THREAD_COUNT, responses.size(), "전체 응답 수 불일치"); +// +// // 3) 예약번호가 모두 유니크한가? +// Set reservationIds = new HashSet<>(); +// for (WaitingResponseDto r : responses) { +// assertNotNull(r.getReservationNumber(), "예약번호가 null 입니다"); +// reservationIds.add(r.getReservationNumber()); +// } +// assertEquals(THREAD_COUNT, reservationIds.size(), "예약번호가 중복되었습니다"); +// +// // 스레드풀 정리 +// exec.shutdown(); +// } +// +// } diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java index 39697323..74ef5038 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/reservation/service/ReservationServiceTest.java @@ -38,6 +38,7 @@ import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -356,7 +357,7 @@ void registerWaiting_LimitExceeded_GWT() { // When & Then assertThatThrownBy(() -> service.registerWaiting(storeId, principal, dto)) - .isInstanceOf(com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException.class) + .isInstanceOf(UserWaitingLimitExceededException.class) .hasMessageContaining("유저당 웨이팅 가능 개수"); verify(waitingRepo, never()).addToWaitingQueue(anyLong(), anyString(), anyInt(), anyLong()); From 3dd0023e75993242dc6ea83ef03919fb40a1172d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:35:53 +0900 Subject: [PATCH 652/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/UserWaitingLimitExceededException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/UserWaitingLimitExceededException.java diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/UserWaitingLimitExceededException.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/UserWaitingLimitExceededException.java new file mode 100644 index 00000000..8f594f9e --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/UserWaitingLimitExceededException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincoreredis.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class UserWaitingLimitExceededException extends RuntimeException { + public UserWaitingLimitExceededException() { + super(ErrorMessage.USER_WAITING_LIMIT_EXCEEDED.getMessage()); + } +} From de996a63adfad6e66d2fbdc4fb48359988582bac Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:37:40 +0900 Subject: [PATCH 653/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingRedisRepository.java | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index a53356f0..e11964f5 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -13,6 +13,7 @@ import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import com.nowait.domaincoreredis.reservation.exception.AlreadyWaitingException; +import com.nowait.domaincoreredis.reservation.exception.UserWaitingLimitExceededException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -80,7 +81,7 @@ public String getUserIdByReservationNumber(Long storeId, String reservationNumbe public void deleteWaiting(Long storeId, String userId) { String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); - String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); Object reservationNumber = redisTemplate.opsForHash().get(numberMapKey, userId); @@ -124,6 +125,8 @@ public Long getWaitingCalledAt(Long storeId, String userId) { public void addWaiting(Long storeId, Long userId, LocalDateTime timestamp) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String userListKey = RedisKeyUtils.buildWaitingUserListKeyPrefix() + userId; + String userWaitingLimitCountKey = RedisKeyUtils.buildUserWaitingLimitCountKey(String.valueOf(userId)); + long score = timestamp .atZone(ZoneId.systemDefault()) .toInstant() @@ -145,25 +148,26 @@ public void addWaiting(Long storeId, Long userId, LocalDateTime timestamp) { public void removeWaiting(Long storeId, Long userId) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; String userListKey = RedisKeyUtils.buildWaitingUserListKeyPrefix() + userId; + String userWaitingLimitCountKey = RedisKeyUtils.buildUserWaitingLimitCountKey(String.valueOf(userId)); - redisTemplate.opsForZSet().remove(queueKey, String.valueOf(userId)); - log.info("웨이팅 대기열 제거 - storeId : {}, userId : {}", storeId, userId); + try { + redisTemplate.opsForZSet().remove(queueKey, String.valueOf(userId)); + log.info("웨이팅 대기열 제거 - storeId : {}, userId : {}", storeId, userId); - redisTemplate.opsForZSet().remove(userListKey, String.valueOf(storeId)); - log.info("유저 웨이팅 목록 제거 - userId : {}, storeId : {}", userId, storeId); - } + redisTemplate.opsForZSet().remove(userListKey, String.valueOf(storeId)); + log.info("유저 웨이팅 목록 제거 - userId : {}, storeId : {}", userId, storeId); - public Long getWaitingRank(Long storeId, Long userId) { - String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; - return redisTemplate.opsForZSet().rank(queueKey, String.valueOf(userId)); - } - - public Long incrementDailySequence(String dailySeqKey) { - return redisTemplate.opsForValue().increment(dailySeqKey, 1); + redisTemplate.opsForValue().decrement(userWaitingLimitCountKey, 1); + log.info("유저 웨이팅 제한 카운트 감소 - userId : {}, currentCount : {}", userId, + redisTemplate.opsForValue().get(userWaitingLimitCountKey)); + } catch (Exception e) { + log.error("Redis 웨이팅 대기열 제거 실패 - storeId : {}, userId : {}, error: {}", storeId, userId, e.getMessage()); + throw e; + } } // 웨이팅 등록 요청 시 멱등키 검증 - public void indempotencyKeyExists(String idempotentKey, String status) { + public void idempotentKeyKeyExists(String idempotentKey, String status) { Boolean success = redisTemplate.opsForValue() .setIfAbsent( idempotentKey, @@ -171,11 +175,40 @@ public void indempotencyKeyExists(String idempotentKey, String status) { Duration.ofSeconds(10) ); + + // TODO 멱등하지 않은 요청 응답값 검토 필요 if (Boolean.FALSE.equals(success)) { throw new AlreadyWaitingException(); } } + public void incrementAndCheckWaitingLimit(Long userId, Long maxLimit) { + String userWaitingLimitCountKey = RedisKeyUtils.buildUserWaitingLimitCountKey(String.valueOf(userId)); + + Long current = redisTemplate.opsForValue().increment(userWaitingLimitCountKey, 1); + log.info("유저 웨이팅 제한 카운트 증가 - userId : {}, currentCount : {}", userId, redisTemplate.opsForValue().get(userWaitingLimitCountKey)); + + // TTL 없으면 하루 단위로 묶어야 함 (중요) + // redisTemplate.expireAt(key, RedisKeyUtils.expireAtNext03()); + + if (current != null && current > maxLimit) { + redisTemplate.opsForValue().decrement(userWaitingLimitCountKey, 1); + throw new UserWaitingLimitExceededException(); + } + } + + // 일일 시퀀스 증가 - 예약 번호 전용 + // TODO : 현재 중복 웨이팅 요청에도 시퀀스가 증가하는 문제가 있음 (추후 개선 필요) + public Long incrementDailySequence(String dailySeqKey) { + return redisTemplate.opsForValue().increment(dailySeqKey, 1); + } + + // TODO : 대기 순번 조회 (추후 사용 예정) + public Long getWaitingRank(Long storeId, Long userId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + return redisTemplate.opsForZSet().rank(queueKey, String.valueOf(userId)); + } + // 웨이팅 여부 조회 // TODO: 구현 필요 public Boolean isWaiting(Long storeId, Long userId) { From e40ee5d658c0b84070a357b65d0c9a963fde0d85 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:38:03 +0900 Subject: [PATCH 654/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=B1=EB=93=B1=EC=84=B1=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/controller/WaitingController.java | 40 +++++++++-- .../waiting/dto/WaitingIdempotencyValue.java | 13 ++++ .../redis/WaitingIdempotencyRepository.java | 53 ++++++++++++++ .../waiting/service/WaitingService.java | 72 ++++++++++++++----- 4 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingIdempotencyValue.java create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java index a333bd8a..220e3025 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java @@ -2,12 +2,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; +import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; import com.nowait.applicationuser.waiting.service.WaitingService; @@ -16,11 +19,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @Tag(name = "Waiting API", description = "예약 API -리팩토링 중-") @RestController -@RequestMapping("/v1/users/me/waitings-refactoring") +@RequestMapping("/v2/users/me/waitings") @RequiredArgsConstructor public class WaitingController { private final WaitingService waitingService; @@ -28,17 +32,19 @@ public class WaitingController { /** * 대기열 리팩토링용 API */ - @PostMapping("/progress/{storeId}") + @PostMapping("/{publicCode}") @Operation(summary = "대기열 리팩토링용 API", description = "대기열 리팩토링용 API") public ResponseEntity registerWaiting( @AuthenticationPrincipal CustomOAuth2User customOAuth2User, - @PathVariable Long storeId, - @RequestBody RegisterWaitingRequest request + @PathVariable String publicCode, + @RequestBody RegisterWaitingRequest request, + HttpServletRequest httpServletRequest ) { RegisterWaitingResponse registerWaitingResponse = waitingService.registerWaiting( customOAuth2User, - storeId, - request + publicCode, + request, + httpServletRequest ); return ResponseEntity @@ -49,4 +55,26 @@ public ResponseEntity registerWaiting( ) ); } + + @DeleteMapping("/{publicCode}") + @Operation(summary = "대기열 리팩토링용 API", description = "대기 취소") + public ResponseEntity cancelWaiting( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @PathVariable String publicCode, + @RequestBody CancelWaitingRequest request + ) { + CancelWaitingResponse cancelWaitingResponse = waitingService.cancelWaiting( + customOAuth2User, + publicCode, + request + ); + + return ResponseEntity + .ok() + .body( + ApiUtils.success( + cancelWaitingResponse + ) + ); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingIdempotencyValue.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingIdempotencyValue.java new file mode 100644 index 00000000..7ccbc99f --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingIdempotencyValue.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class WaitingIdempotencyValue { + private String state; + private RegisterWaitingResponse response; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java new file mode 100644 index 00000000..791b3880 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -0,0 +1,53 @@ +package com.nowait.applicationuser.waiting.redis; + +import java.time.Duration; +import java.util.Optional; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class WaitingIdempotencyRepository { + + private final RedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + + private static final Duration TTL = Duration.ofMinutes(10); + + public Optional findByKey(String key) { + String value = redisTemplate.opsForValue().get(key); + + if (value == null) { + return Optional.empty(); + } + + try { + return Optional.of( + objectMapper.readValue(value, WaitingIdempotencyValue.class) + ); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to deserialize value from Redis", e); + } + } + + public void saveIdempotencyValue(String key, RegisterWaitingResponse response) { + WaitingIdempotencyValue waitingIdempotencyValue = new WaitingIdempotencyValue( + "COMPLETED", + response + ); + + try { + String jsonValue = objectMapper.writeValueAsString(waitingIdempotencyValue); + redisTemplate.opsForValue().set(key, jsonValue, TTL); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to serialize value for Redis", e); + } + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index caa47e6a..e94f024d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -2,16 +2,22 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; +import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; +import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; @@ -23,10 +29,13 @@ import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Service @RequiredArgsConstructor +@Slf4j public class WaitingService { private final ReservationRepository reservationRepository; @@ -34,30 +43,44 @@ public class WaitingService { private final StoreRepository storeRepository; private final UserRepository userRepository; private final ApplicationEventPublisher eventPublisher; + private final WaitingIdempotencyRepository waitingIdempotencyRepository; /** * 최초 대기 등록 - * @param storeId + * @param publicCode * @param waitingRequest */ // 대기열 리팩토링 서비스 메서드 @Transactional - public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Long storeId, RegisterWaitingRequest waitingRequest) { + public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, String publicCode, RegisterWaitingRequest waitingRequest, HttpServletRequest httpServletRequest) { // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 - Store store = storeRepository.findById(storeId) + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); User user = userRepository.findById(oAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); // 웨이팅 고유 번호 생성 - YYYYMMDD-storeId-sequence number 일련 번호 + Long storeId = store.getStoreId(); LocalDateTime timestamp = LocalDateTime.now(); String waitingNumber = generateWaitingNumber(storeId, timestamp); - String idempotentKey = generateIdempotentKey(storeId, user.getId()); + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - waitingRedisRepository.indempotencyKeyExists(idempotentKey, ReservationStatus.WAITING.name()); + // waitingRedisRepository.idempotentKeyKeyExists(idempotentKey, ReservationStatus.WAITING.name()); + + // TODO 멱등성 검증 로직 점검 필요 + Optional existingIdempotencyValue = waitingIdempotencyRepository.findByKey(idempotentKey); + if (existingIdempotencyValue.isPresent()) { + log.info("Existing idempotency key found: {}", idempotentKey); + return existingIdempotencyValue.get().getResponse(); + } + + + // 일일 가능 웨이팅 최대 개수 초과 검증 + // TODO race condition 발생 가능성 점검 필요 + waitingRedisRepository.incrementAndCheckWaitingLimit(user.getId(), 3L); // DB에 상태 값 저장 Reservation reservation = Reservation.builder() @@ -81,30 +104,46 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Long ) ); - return RegisterWaitingResponse.builder() + RegisterWaitingResponse response = RegisterWaitingResponse.builder() .waitingNumber(waitingNumber) .partySize(waitingRequest.getPartySize()) .build(); + + // 멱등키가 있다면 멱등 응답 저장 + waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + + return response; } @Transactional - public void cancelWaiting(CustomOAuth2User oAuth2User, Long storeId) { - // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 - Store store = storeRepository.findById(storeId) - .orElseThrow(StoreNotFoundException::new); + public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String publicCode, CancelWaitingRequest request) { + + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode).orElseThrow(StoreNotFoundException::new); + Long storeId = store.getStoreId(); User user = userRepository.findById(oAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); - String idempotentKey = generateIdempotentKey(storeId, user.getId()); - waitingRedisRepository.indempotencyKeyExists(idempotentKey, ReservationStatus.CANCELLED.name()); + // TODO 멱등키 검증 로직 점검 필요 + // String idempotentKey = generateIdempotentKey(storeId, user.getId()); + // waitingRedisRepository.idempotentKeyKeyExists(idempotentKey, ReservationStatus.CANCELLED.name()); - // DB에 상태 값 저장 - // TODO: 웨이팅 취소 로직 구현 필요 (예: 상태 변경, 취소 시간 기록 등) + // DB 웨이팅 상태 취소 처리 + Reservation reservation = reservationRepository.findReservationByReservationNumber(request.getWaitingNumber()) + .orElseThrow(ReservationNotFoundException::new); - // Redis 대기열 취소 이벤트 발행 + reservation.markAsCancelled(LocalDateTime.now()); - return; + // Redis 대기열 취소 이벤트 발행 + waitingRedisRepository.removeWaiting(storeId, user.getId()); + + return CancelWaitingResponse.builder() + .waitingNumber(reservation.getReservationNumber()) + .storeId(storeId) + .reservationStatus(reservation.getStatus()) + .canceledAt(reservation.getUpdatedAt()) + .message("대기 취소가 완료되었습니다.") + .build(); } private String generateWaitingNumber(Long storeId, LocalDateTime timestamp) { @@ -112,6 +151,7 @@ private String generateWaitingNumber(Long storeId, LocalDateTime timestamp) { String today = timestamp.format(DateTimeFormatter.BASIC_ISO_DATE); // YYYYMMDD // atomic increment + // TODO 웨이팅 실패 시 카운터 롤백 처리 필요 String dailySeqKey = RedisKeyUtils.buildWaitingSeqKey(storeId) + ":" + today; // ex. waiting:sequence:{storeId}:{today} Long seqNum = waitingRedisRepository.incrementDailySequence(dailySeqKey); From 9a94ba518bb3e841e092991f23e7dc12df510d5f Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:38:16 +0900 Subject: [PATCH 655/685] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/UserWaitingLimitExceededException.java | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java deleted file mode 100644 index dccdff12..00000000 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nowait.domaincorerdb.reservation.exception; - -import com.nowait.common.exception.ErrorMessage; - -public class UserWaitingLimitExceededException extends RuntimeException { - public UserWaitingLimitExceededException() { - super(ErrorMessage.USER_WAITING_LIMIT_EXCEEDED.getMessage()); - } -} - From a248f730a3f1d47d566ef9bdf26ec038bfab3558 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:38:38 +0900 Subject: [PATCH 656/685] =?UTF-8?q?refactor:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 178 +++++++++--------- 1 file changed, 90 insertions(+), 88 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index 6d6714fc..61a0cd4a 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -3,8 +3,6 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import java.util.Optional; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,17 +14,18 @@ import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; +import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; -import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; -import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; +import jakarta.servlet.http.HttpServletRequest; + @ExtendWith(MockitoExtension.class) class WaitingServiceTest { @@ -42,6 +41,8 @@ class WaitingServiceTest { private WaitingRedisRepository waitingRedisRepository; @Mock private ReservationRepository reservationRepository; + @Mock + WaitingIdempotencyRepository waitingIdempotencyRepository; @Test @DisplayName("웨이팅 정상 등록 테스트") @@ -50,21 +51,22 @@ void registerWaiting() { CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); RegisterWaitingRequest request = new RegisterWaitingRequest(4); - Long storeId = 1L; + String publicCode = "ZiVXAD1vVr5b"; Long userId = 1L; + HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); - Store store = Store.builder().storeId(storeId).build(); + Store store = Store.builder().publicCode(publicCode).build(); User user = User.builder().id(userId).build(); // when - when(storeRepository.findById(storeId)).thenReturn(java.util.Optional.of(store)); + when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); when(customOAuth2User.getUserId()).thenReturn(userId); when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); - RegisterWaitingResponse response = waitingService.registerWaiting(customOAuth2User, storeId, request); + RegisterWaitingResponse response = waitingService.registerWaiting(customOAuth2User, publicCode, request, httpServletRequest); // then - verify(waitingRedisRepository).indempotencyKeyExists(anyString(), eq("WAITING")); + verify(waitingIdempotencyRepository).findByKey(anyString()); verify(reservationRepository).save(any(Reservation.class)); verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); @@ -72,83 +74,83 @@ void registerWaiting() { assertThat(response.getWaitingNumber()).isNotNull(); } - @Test - @DisplayName("DB 저장 실패 시 Redis 대기열 추가 이벤트 실행 되지 않음") - void registerWaiting_dbSaveFail() { - // given - CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - RegisterWaitingRequest request = new RegisterWaitingRequest(4); - - Long storeId = 1L; - Long userId = 1L; - - Store store = Store.builder().storeId(storeId).build(); - User user = User.builder().id(userId).build(); - - // when - when(customOAuth2User.getUserId()).thenReturn(userId); - when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); - when(userRepository.findById(userId)).thenReturn(Optional.of(user)); - - when(reservationRepository.save(any(Reservation.class))).thenThrow(new RuntimeException("DB 저장 실패")); - - // then - assertThatThrownBy(() -> - waitingService.registerWaiting(customOAuth2User, storeId, request) - ).isInstanceOf(RuntimeException.class); - - verify(waitingRedisRepository).indempotencyKeyExists(anyString(), eq("WAITING")); - verify(eventPublisher, never()).publishEvent(any()); - } - - @Test - @DisplayName("없는 주점에 웨이팅 등록 시도 시 예외 발생") - void registerWaiting_storeNotFound() { - // given - CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - RegisterWaitingRequest request = new RegisterWaitingRequest(4); - - Long storeId = -1L; // 존재하지 않는 주점 ID - Long userId = 1L; - - // when - when(storeRepository.findById(storeId)).thenReturn(Optional.empty()); - - // then - assertThatThrownBy(() -> - waitingService.registerWaiting(customOAuth2User, storeId, request) - ).isInstanceOf(StoreNotFoundException.class); - - verify(userRepository, never()).findById(anyLong()); - verify(waitingRedisRepository, never()).indempotencyKeyExists(anyString(), eq("WAITING")); - verify(reservationRepository, never()).save(any(Reservation.class)); - verify(eventPublisher, never()).publishEvent(any()); - } - - @Test - @DisplayName("없는 유저가 웨이팅 등록 시도 시 예외 발생") - void registerWaiting_userNotFound() { - // given - CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - RegisterWaitingRequest request = new RegisterWaitingRequest(4); - - Long storeId = 1L; - Long userId = -1L; // 존재 하지 않는 유저 ID - - Store store = Store.builder().storeId(storeId).build(); - - // when - when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); - when(customOAuth2User.getUserId()).thenReturn(userId); - when(userRepository.findById(userId)).thenReturn(Optional.empty()); - - // then - assertThatThrownBy(() -> - waitingService.registerWaiting(customOAuth2User, storeId, request) - ).isInstanceOf(UserNotFoundException.class); - - verify(waitingRedisRepository, never()).indempotencyKeyExists(anyString(), eq("WAITING")); - verify(reservationRepository, never()).save(any(Reservation.class)); - verify(eventPublisher, never()).publishEvent(any()); - } + // @Test + // @DisplayName("DB 저장 실패 시 Redis 대기열 추가 이벤트 실행 되지 않음") + // void registerWaiting_dbSaveFail() { + // // given + // CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + // RegisterWaitingRequest request = new RegisterWaitingRequest(4); + // + // Long storeId = 1L; + // Long userId = 1L; + // + // Store store = Store.builder().storeId(storeId).build(); + // User user = User.builder().id(userId).build(); + // + // // when + // when(customOAuth2User.getUserId()).thenReturn(userId); + // when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + // when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + // + // when(reservationRepository.save(any(Reservation.class))).thenThrow(new RuntimeException("DB 저장 실패")); + // + // // then + // assertThatThrownBy(() -> + // waitingService.registerWaiting(customOAuth2User, storeId, request) + // ).isInstanceOf(RuntimeException.class); + // + // verify(waitingRedisRepository).idempotentKeyKeyExists(anyString(), eq("WAITING")); + // verify(eventPublisher, never()).publishEvent(any()); + // } + // + // @Test + // @DisplayName("없는 주점에 웨이팅 등록 시도 시 예외 발생") + // void registerWaiting_storeNotFound() { + // // given + // CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + // RegisterWaitingRequest request = new RegisterWaitingRequest(4); + // + // Long storeId = -1L; // 존재하지 않는 주점 ID + // Long userId = 1L; + // + // // when + // when(storeRepository.findById(storeId)).thenReturn(Optional.empty()); + // + // // then + // assertThatThrownBy(() -> + // waitingService.registerWaiting(customOAuth2User, storeId, request) + // ).isInstanceOf(StoreNotFoundException.class); + // + // verify(userRepository, never()).findById(anyLong()); + // verify(waitingRedisRepository, never()).idempotentKeyKeyExists(anyString(), eq("WAITING")); + // verify(reservationRepository, never()).save(any(Reservation.class)); + // verify(eventPublisher, never()).publishEvent(any()); + // } + // + // @Test + // @DisplayName("없는 유저가 웨이팅 등록 시도 시 예외 발생") + // void registerWaiting_userNotFound() { + // // given + // CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + // RegisterWaitingRequest request = new RegisterWaitingRequest(4); + // + // Long storeId = 1L; + // Long userId = -1L; // 존재 하지 않는 유저 ID + // + // Store store = Store.builder().storeId(storeId).build(); + // + // // when + // when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); + // when(customOAuth2User.getUserId()).thenReturn(userId); + // when(userRepository.findById(userId)).thenReturn(Optional.empty()); + // + // // then + // assertThatThrownBy(() -> + // waitingService.registerWaiting(customOAuth2User, storeId, request) + // ).isInstanceOf(UserNotFoundException.class); + // + // verify(waitingRedisRepository, never()).idempotentKeyKeyExists(anyString(), eq("WAITING")); + // verify(reservationRepository, never()).save(any(Reservation.class)); + // verify(eventPublisher, never()).publishEvent(any()); + // } } From d303b9d6af9b7fb96655cae97f2db3628127d569 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:38:45 +0900 Subject: [PATCH 657/685] =?UTF-8?q?refactor:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java index 2416e0d5..57ca9cb5 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java @@ -1,7 +1,5 @@ package com.nowait.applicationuser.reservation.service; -import static com.nowait.common.exception.ErrorMessage.*; - import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; @@ -36,7 +34,6 @@ import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; -import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.ImageType; import com.nowait.domaincorerdb.store.entity.Store; @@ -49,6 +46,7 @@ import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.common.util.RedisKeyUtils; +import com.nowait.domaincoreredis.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincoreredis.reservation.repository.WaitingPermitLuaRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; From 117fa9371a01a4833d4a1d22141d55a54743ee92 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:52:45 +0900 Subject: [PATCH 658/685] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/exception/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 67fef84d..de20f1f3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -229,7 +229,7 @@ public ErrorResponse duplicateReservationException(DuplicateReservationException public ErrorResponse alreadyWaitingException(AlreadyDeletedWaitingException e, WebRequest request) { alarm(e, request); log.error("alreadyWaitingException", e); - return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); + return new ErrorResponse(e.getMessage(), ALREADY_DELETED_RESERVATION.getCode()); } @ResponseStatus(BAD_REQUEST) From d00b1c5201bb1017998ec5c8af5edfb7e58b0f0d Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 21:54:39 +0900 Subject: [PATCH 659/685] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=EC=84=B1?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingService.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index e94f024d..e1165c13 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -54,6 +54,15 @@ public class WaitingService { @Transactional public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, String publicCode, RegisterWaitingRequest waitingRequest, HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + // TODO 멱등성 검증 로직 점검 필요 + Optional existingIdempotencyValue = waitingIdempotencyRepository.findByKey(idempotentKey); + if (existingIdempotencyValue.isPresent()) { + log.info("Existing idempotency key found: {}", idempotentKey); + return existingIdempotencyValue.get().getResponse(); + } + // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); @@ -65,18 +74,10 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri Long storeId = store.getStoreId(); LocalDateTime timestamp = LocalDateTime.now(); String waitingNumber = generateWaitingNumber(storeId, timestamp); - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 // waitingRedisRepository.idempotentKeyKeyExists(idempotentKey, ReservationStatus.WAITING.name()); - // TODO 멱등성 검증 로직 점검 필요 - Optional existingIdempotencyValue = waitingIdempotencyRepository.findByKey(idempotentKey); - if (existingIdempotencyValue.isPresent()) { - log.info("Existing idempotency key found: {}", idempotentKey); - return existingIdempotencyValue.get().getResponse(); - } - // 일일 가능 웨이팅 최대 개수 초과 검증 // TODO race condition 발생 가능성 점검 필요 From b3b45bd3fe7a46a9240fcdf6996d57206b53bc56 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 22:53:22 +0900 Subject: [PATCH 660/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/controller/WaitingController.java | 22 +++++++++++++++++++ .../waiting/dto/GetWaitingSizeResponse.java | 11 ++++++++++ .../waiting/service/WaitingService.java | 19 ++++++++++++++++ .../repository/WaitingRedisRepository.java | 7 ++++++ 4 files changed, 59 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java index 220e3025..e49dda65 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java @@ -3,6 +3,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -11,6 +12,7 @@ import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; +import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; import com.nowait.applicationuser.waiting.service.WaitingService; @@ -77,4 +79,24 @@ public ResponseEntity cancelWaiting( ) ); } + + @GetMapping("/{publicCode}/waiting-count") + @Operation(summary = "대기열 리팩토링용 API", description = "대기 인원수 조회") + public ResponseEntity getWaitingCount( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User, + @PathVariable String publicCode + ) { + GetWaitingSizeResponse response = waitingService.getWaitingCount( + customOAuth2User, + publicCode + ); + + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java new file mode 100644 index 00000000..85a0f93a --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java @@ -0,0 +1,11 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetWaitingSizeResponse { + private Long storeId; + private Long waitingCount; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index e94f024d..931cdaa2 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -10,6 +10,7 @@ import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; +import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; @@ -146,6 +147,24 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .build(); } + public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, String publicCode) { + + Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) + .orElseThrow(StoreNotFoundException::new); + + User user = userRepository.findById(oAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + + Long storeId = store.getStoreId(); + + Long waitingCount = waitingRedisRepository.getWaitingCount(storeId); + + return GetWaitingSizeResponse.builder() + .storeId(storeId) + .waitingCount(waitingCount) + .build(); + } + private String generateWaitingNumber(Long storeId, LocalDateTime timestamp) { // 1) 키 접두사 - 날짜 String today = timestamp.format(DateTimeFormatter.BASIC_ISO_DATE); // YYYYMMDD diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index e11964f5..f5f69441 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -209,6 +209,13 @@ public Long getWaitingRank(Long storeId, Long userId) { return redisTemplate.opsForZSet().rank(queueKey, String.valueOf(userId)); } + public Long getWaitingCount(Long storeId) { + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + Long size = redisTemplate.opsForZSet().zCard(queueKey); + + return size != null ? size : 0L; + } + // 웨이팅 여부 조회 // TODO: 구현 필요 public Boolean isWaiting(Long storeId, Long userId) { From 69ccbbc6f70d7e06541a2dc28f62bf242502a449 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 23:11:09 +0900 Subject: [PATCH 661/685] =?UTF-8?q?feat:=20=ED=95=99=EA=B3=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B4=80=EB=A0=A8=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 9 +++++++++ .../waiting/service/WaitingService.java | 11 ++++++++++- .../com/nowait/common/exception/ErrorMessage.java | 3 +++ .../exception/DepartmentNotFoundException.java | 9 +++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/exception/DepartmentNotFoundException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index de20f1f3..3eb701b7 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -24,6 +24,7 @@ import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; import com.nowait.discord.service.DiscordAlarmService; +import com.nowait.domaincorerdb.department.exception.DepartmentNotFoundException; import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; @@ -240,6 +241,14 @@ public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException return new ErrorResponse(e.getMessage(), STORE_WAITING_DISABLED.getCode()); } + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(DepartmentNotFoundException.class) + public ErrorResponse handleDepartmentNotFoundException(DepartmentNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("handleDepartmentNotFoundException", e); + return new ErrorResponse(e.getMessage(), DEPARTMENT_NOT_FOUND.getCode()); + } + @ResponseStatus(NOT_FOUND) @ExceptionHandler(StoreNotFoundException.class) public ErrorResponse handleStoreNotFoundException(StoreNotFoundException e, WebRequest request) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index 8a822b69..822d31c3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -17,6 +17,9 @@ import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.department.entity.Department; +import com.nowait.domaincorerdb.department.exception.DepartmentNotFoundException; +import com.nowait.domaincorerdb.department.repository.DepartmentRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; @@ -43,6 +46,7 @@ public class WaitingService { private final WaitingRedisRepository waitingRedisRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; + private final DepartmentRepository departmentRepository; private final ApplicationEventPublisher eventPublisher; private final WaitingIdempotencyRepository waitingIdempotencyRepository; @@ -153,15 +157,20 @@ public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, Strin Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); + Long storeId = store.getStoreId(); + User user = userRepository.findById(oAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); - Long storeId = store.getStoreId(); + Department department = departmentRepository.findById(store.getDepartmentId()) + .orElseThrow(DepartmentNotFoundException::new); Long waitingCount = waitingRedisRepository.getWaitingCount(storeId); return GetWaitingSizeResponse.builder() .storeId(storeId) + .storeName(store.getName()) + .departmentName(department.getName()) .waitingCount(waitingCount) .build(); } diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index fa9e0bd7..ba58c78a 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -79,6 +79,9 @@ public enum ErrorMessage { STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment006"), STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment007"), + // department + DEPARTMENT_NOT_FOUND("해당 학과/전공을 찾을 수 없습니다.", "department001"), + // Statistics STATISTIC_VIEW_UNAUTHORIZED("통계 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "statistics001"), MENU_COUNTER_UPDATE("메뉴 카운터 업데이트 실패", "statistics002"), diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/exception/DepartmentNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/exception/DepartmentNotFoundException.java new file mode 100644 index 00000000..55718143 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/exception/DepartmentNotFoundException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.department.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class DepartmentNotFoundException extends RuntimeException { + public DepartmentNotFoundException() { + super(ErrorMessage.DEPARTMENT_NOT_FOUND.getMessage()); + } +} From 3e35faadc4540409bcb9838ff0d0b88279a5c263 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 1 Feb 2026 23:11:18 +0900 Subject: [PATCH 662/685] =?UTF-8?q?feat:=20=ED=95=99=EA=B3=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=B0=8F=20=EC=A3=BC=EC=A0=90=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/waiting/dto/GetWaitingSizeResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java index 85a0f93a..17d654ff 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetWaitingSizeResponse.java @@ -7,5 +7,7 @@ @Builder public class GetWaitingSizeResponse { private Long storeId; + private String storeName; + private String departmentName; private Long waitingCount; } From 2ef30177c8b5fbea969d48b3ac0b69f122a7c464 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 00:04:07 +0900 Subject: [PATCH 663/685] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/redis/WaitingIdempotencyRepository.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 791b3880..50f4f1f4 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -21,22 +21,25 @@ public class WaitingIdempotencyRepository { private static final Duration TTL = Duration.ofMinutes(10); + // 멱등키 조회 메서드 public Optional findByKey(String key) { - String value = redisTemplate.opsForValue().get(key); + String idempotencyValue = redisTemplate.opsForValue().get(key); - if (value == null) { + if (idempotencyValue == null) { return Optional.empty(); } try { return Optional.of( - objectMapper.readValue(value, WaitingIdempotencyValue.class) + objectMapper.readValue(idempotencyValue, WaitingIdempotencyValue.class) ); } catch (Exception e) { throw new IllegalArgumentException("Failed to deserialize value from Redis", e); } } + + // 멱등키 저장 메서드 public void saveIdempotencyValue(String key, RegisterWaitingResponse response) { WaitingIdempotencyValue waitingIdempotencyValue = new WaitingIdempotencyValue( "COMPLETED", From c90e3c710ced76f598caf3641aa6d707fa4657f8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 00:04:30 +0900 Subject: [PATCH 664/685] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WaitingRedisRepository.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index f5f69441..b0b74bc2 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -166,22 +166,6 @@ public void removeWaiting(Long storeId, Long userId) { } } - // 웨이팅 등록 요청 시 멱등키 검증 - public void idempotentKeyKeyExists(String idempotentKey, String status) { - Boolean success = redisTemplate.opsForValue() - .setIfAbsent( - idempotentKey, - status, - Duration.ofSeconds(10) - ); - - - // TODO 멱등하지 않은 요청 응답값 검토 필요 - if (Boolean.FALSE.equals(success)) { - throw new AlreadyWaitingException(); - } - } - public void incrementAndCheckWaitingLimit(Long userId, Long maxLimit) { String userWaitingLimitCountKey = RedisKeyUtils.buildUserWaitingLimitCountKey(String.valueOf(userId)); From a3a03d4a8ec7a90f1abc29e784c015077e6ec808 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 00:04:51 +0900 Subject: [PATCH 665/685] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingService.java | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index 822d31c3..cb2e7ff0 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -59,13 +59,10 @@ public class WaitingService { @Transactional public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, String publicCode, RegisterWaitingRequest waitingRequest, HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - // TODO 멱등성 검증 로직 점검 필요 - Optional existingIdempotencyValue = waitingIdempotencyRepository.findByKey(idempotentKey); - if (existingIdempotencyValue.isPresent()) { - log.info("Existing idempotency key found: {}", idempotentKey); - return existingIdempotencyValue.get().getResponse(); + RegisterWaitingResponse registerWaitingResponse = validateIdempotency(httpServletRequest); + if (registerWaitingResponse != null) { + log.info("Idempotent request detected. Returning existing response."); + return registerWaitingResponse; } // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 @@ -75,19 +72,15 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri User user = userRepository.findById(oAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); + // 일일 가능 웨이팅 최대 개수 초과 검증 + // TODO race condition 발생 가능성 점검 필요, DB 저장 로직 실패 시 롤백 처리 필요 + waitingRedisRepository.incrementAndCheckWaitingLimit(user.getId(), 3L); + // 웨이팅 고유 번호 생성 - YYYYMMDD-storeId-sequence number 일련 번호 Long storeId = store.getStoreId(); LocalDateTime timestamp = LocalDateTime.now(); String waitingNumber = generateWaitingNumber(storeId, timestamp); - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // waitingRedisRepository.idempotentKeyKeyExists(idempotentKey, ReservationStatus.WAITING.name()); - - - // 일일 가능 웨이팅 최대 개수 초과 검증 - // TODO race condition 발생 가능성 점검 필요 - waitingRedisRepository.incrementAndCheckWaitingLimit(user.getId(), 3L); - // DB에 상태 값 저장 Reservation reservation = Reservation.builder() .reservationNumber(waitingNumber) @@ -115,8 +108,8 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri .partySize(waitingRequest.getPartySize()) .build(); - // 멱등키가 있다면 멱등 응답 저장 - waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + // TODO 멱등키 응답 실패 시 어떻게 처리할 지 점검 필요 + saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } @@ -131,8 +124,11 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .orElseThrow(UserNotFoundException::new); // TODO 멱등키 검증 로직 점검 필요 - // String idempotentKey = generateIdempotentKey(storeId, user.getId()); - // waitingRedisRepository.idempotentKeyKeyExists(idempotentKey, ReservationStatus.CANCELLED.name()); + // Optional existingIdempotencyValue = waitingIdempotencyRepository.findByKey(idempotentKey); + // if (existingIdempotencyValue.isPresent()) { + // log.info("Existing idempotency key found: {}", idempotentKey); + // return existingIdempotencyValue.get().getResponse(); + // } // DB 웨이팅 상태 취소 처리 Reservation reservation = reservationRepository.findReservationByReservationNumber(request.getWaitingNumber()) @@ -143,28 +139,56 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p // Redis 대기열 취소 이벤트 발행 waitingRedisRepository.removeWaiting(storeId, user.getId()); - return CancelWaitingResponse.builder() + CancelWaitingResponse response = CancelWaitingResponse.builder() .waitingNumber(reservation.getReservationNumber()) .storeId(storeId) .reservationStatus(reservation.getStatus()) .canceledAt(reservation.getUpdatedAt()) .message("대기 취소가 완료되었습니다.") .build(); + + // 멱등키가 있다면 멱등 응답 저장 + // waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + + return response; } + // 멱등키 검증 메서드 + private RegisterWaitingResponse validateIdempotency(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + if (idempotentKey == null || idempotentKey.isBlank()) { + return null; + } + + // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 + // TODO 멱등성 검증 로직 점검 필요 + return waitingIdempotencyRepository.findByKey(idempotentKey) + .map(WaitingIdempotencyValue::getResponse) + .orElse(null); + } + + // 멱등키 응답 저장 메서드 + private void saveIdempotencyResponse(String idempotentKey, RegisterWaitingResponse response) { + if (idempotentKey != null && !idempotentKey.isBlank()) { + waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + } + } + + // 현재 대기 인원 수 조회 public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, String publicCode) { Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); - Long storeId = store.getStoreId(); - User user = userRepository.findById(oAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); Department department = departmentRepository.findById(store.getDepartmentId()) .orElseThrow(DepartmentNotFoundException::new); + Long storeId = store.getStoreId(); + Long waitingCount = waitingRedisRepository.getWaitingCount(storeId); return GetWaitingSizeResponse.builder() @@ -190,8 +214,4 @@ private String generateWaitingNumber(Long storeId, LocalDateTime timestamp) { // 4) 최종 ID 조합 return today + "-" + storeId + "-" + seqStr; } - - private String generateIdempotentKey(Long storeId, Long userId) { - return "idempotentKey" + ":" + storeId + ":" + userId; - } } From 08c7bfd1d2f6536a8f0c07fd776aad391ab03e17 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 00:05:10 +0900 Subject: [PATCH 666/685] =?UTF-8?q?test:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=9C=A0=EB=8B=9B=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 214 ++++++++++-------- 1 file changed, 122 insertions(+), 92 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index 61a0cd4a..d7b1fe2c 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -3,6 +3,9 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -13,6 +16,7 @@ import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; @@ -21,6 +25,7 @@ import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -42,115 +47,140 @@ class WaitingServiceTest { @Mock private ReservationRepository reservationRepository; @Mock - WaitingIdempotencyRepository waitingIdempotencyRepository; + private WaitingIdempotencyRepository waitingIdempotencyRepository; + @Mock + HttpServletRequest httpServletRequest; + @Mock + CustomOAuth2User customOAuth2User; + + private static final String IDEMPOTENCY_KEY = "550e8400-e29b-41d4-a716-446655440000"; + + @BeforeEach + void setUp() { + when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); + } @Test - @DisplayName("웨이팅 정상 등록 테스트") - void registerWaiting() { + @DisplayName("멱등키 있는 경우 미리 저장된 응답이 반환 되는지 테스트") + void registerWaiting_idempotentKeyExists() { + // given + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + RegisterWaitingResponse idempotentResponse = RegisterWaitingResponse.builder() + .waitingNumber("20260201-2-0001") + .partySize(4) + .build(); + + when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.of(new WaitingIdempotencyValue( + "COMPLETED", + idempotentResponse + ))); + + // when + RegisterWaitingResponse response = waitingService.registerWaiting( + customOAuth2User, + "ZiVXAD1vVr5b", + request, + httpServletRequest + ); + + // then + assertThat(response).isSameAs(idempotentResponse); + + verify(storeRepository, never()).findByPublicCodeAndDeletedFalse(anyString()); + verify(userRepository, never()).findById(anyLong()); + verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + } + + @Test + @DisplayName("웨이팅 개수 제한 초과 시 예외 발생 테스트") + void registerWaiting_exceedWaitingLimit() { // given CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); RegisterWaitingRequest request = new RegisterWaitingRequest(4); + when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); + when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.empty()); + + + Long userId = 10L; String publicCode = "ZiVXAD1vVr5b"; - Long userId = 1L; - HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); Store store = Store.builder().publicCode(publicCode).build(); User user = User.builder().id(userId).build(); - // when when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); - when(customOAuth2User.getUserId()).thenReturn(userId); + when(customOAuth2User.getUserId()).thenReturn(10L); when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); - RegisterWaitingResponse response = waitingService.registerWaiting(customOAuth2User, publicCode, request, httpServletRequest); + + doThrow(new UserWaitingLimitExceededException()) + .when(waitingRedisRepository) + .incrementAndCheckWaitingLimit(10L, 3L); // then - verify(waitingIdempotencyRepository).findByKey(anyString()); - verify(reservationRepository).save(any(Reservation.class)); - verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); + assertThatThrownBy(() -> waitingService.registerWaiting( + customOAuth2User, + "ZiVXAD1vVr5b", + request, + httpServletRequest + )).isInstanceOf(UserWaitingLimitExceededException.class); - assertThat(response.getPartySize()).isEqualTo(4); - assertThat(response.getWaitingNumber()).isNotNull(); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingRedisRepository).incrementAndCheckWaitingLimit(10L, 3L); } - // @Test - // @DisplayName("DB 저장 실패 시 Redis 대기열 추가 이벤트 실행 되지 않음") - // void registerWaiting_dbSaveFail() { - // // given - // CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - // RegisterWaitingRequest request = new RegisterWaitingRequest(4); - // - // Long storeId = 1L; - // Long userId = 1L; - // - // Store store = Store.builder().storeId(storeId).build(); - // User user = User.builder().id(userId).build(); - // - // // when - // when(customOAuth2User.getUserId()).thenReturn(userId); - // when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); - // when(userRepository.findById(userId)).thenReturn(Optional.of(user)); - // - // when(reservationRepository.save(any(Reservation.class))).thenThrow(new RuntimeException("DB 저장 실패")); - // - // // then - // assertThatThrownBy(() -> - // waitingService.registerWaiting(customOAuth2User, storeId, request) - // ).isInstanceOf(RuntimeException.class); - // - // verify(waitingRedisRepository).idempotentKeyKeyExists(anyString(), eq("WAITING")); - // verify(eventPublisher, never()).publishEvent(any()); - // } - // - // @Test - // @DisplayName("없는 주점에 웨이팅 등록 시도 시 예외 발생") - // void registerWaiting_storeNotFound() { - // // given - // CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - // RegisterWaitingRequest request = new RegisterWaitingRequest(4); - // - // Long storeId = -1L; // 존재하지 않는 주점 ID - // Long userId = 1L; - // - // // when - // when(storeRepository.findById(storeId)).thenReturn(Optional.empty()); - // - // // then - // assertThatThrownBy(() -> - // waitingService.registerWaiting(customOAuth2User, storeId, request) - // ).isInstanceOf(StoreNotFoundException.class); - // - // verify(userRepository, never()).findById(anyLong()); - // verify(waitingRedisRepository, never()).idempotentKeyKeyExists(anyString(), eq("WAITING")); - // verify(reservationRepository, never()).save(any(Reservation.class)); - // verify(eventPublisher, never()).publishEvent(any()); - // } - // - // @Test - // @DisplayName("없는 유저가 웨이팅 등록 시도 시 예외 발생") - // void registerWaiting_userNotFound() { - // // given - // CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - // RegisterWaitingRequest request = new RegisterWaitingRequest(4); - // - // Long storeId = 1L; - // Long userId = -1L; // 존재 하지 않는 유저 ID - // - // Store store = Store.builder().storeId(storeId).build(); - // - // // when - // when(storeRepository.findById(storeId)).thenReturn(Optional.of(store)); - // when(customOAuth2User.getUserId()).thenReturn(userId); - // when(userRepository.findById(userId)).thenReturn(Optional.empty()); - // - // // then - // assertThatThrownBy(() -> - // waitingService.registerWaiting(customOAuth2User, storeId, request) - // ).isInstanceOf(UserNotFoundException.class); - // - // verify(waitingRedisRepository, never()).idempotentKeyKeyExists(anyString(), eq("WAITING")); - // verify(reservationRepository, never()).save(any(Reservation.class)); - // verify(eventPublisher, never()).publishEvent(any()); - // } + + @Test + @DisplayName("웨이팅 정상 등록 시 DB 저장, 이벤트 발생, 멱등 응답 저장 수행") + void registerWaiting_success() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); + when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.empty()); + + Long userId = 10L; + String publicCode = "ZiVXAD1vVr5b"; + + Store store = Store.builder().publicCode(publicCode).build(); + User user = User.builder().id(userId).build(); + + when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(10L); + when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); + + doNothing() + .when(waitingRedisRepository) + .incrementAndCheckWaitingLimit(userId, 3L); + + when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); + + // when + RegisterWaitingResponse response = waitingService.registerWaiting( + customOAuth2User, + publicCode, + request, + httpServletRequest + ); + + // then + assertThat(response).isNotNull(); + assertThat(response.getPartySize()).isEqualTo(4); + assertThat(response.getWaitingNumber()).isNotBlank(); + + verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); + verify(reservationRepository).save(any(Reservation.class)); + verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); + verify(waitingIdempotencyRepository).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + } } From b594ca53d55925d2f2b1c423c8eca097d1b9566c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 23:09:34 +0900 Subject: [PATCH 667/685] =?UTF-8?q?test:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=98=88=EC=99=B8=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 152 ++++++++++++++---- 1 file changed, 124 insertions(+), 28 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index d7b1fe2c..2e407abc 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -22,8 +22,10 @@ import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domaincoreredis.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincoreredis.reservation.repository.WaitingRedisRepository; @@ -49,9 +51,9 @@ class WaitingServiceTest { @Mock private WaitingIdempotencyRepository waitingIdempotencyRepository; @Mock - HttpServletRequest httpServletRequest; + private HttpServletRequest httpServletRequest; @Mock - CustomOAuth2User customOAuth2User; + private CustomOAuth2User customOAuth2User; private static final String IDEMPOTENCY_KEY = "550e8400-e29b-41d4-a716-446655440000"; @@ -97,8 +99,8 @@ void registerWaiting_idempotentKeyExists() { } @Test - @DisplayName("웨이팅 개수 제한 초과 시 예외 발생 테스트") - void registerWaiting_exceedWaitingLimit() { + @DisplayName("웨이팅 정상 등록 시 DB 저장, 이벤트 발생, 멱등 응답 저장 수행") + void registerWaiting_success() { // given CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); RegisterWaitingRequest request = new RegisterWaitingRequest(4); @@ -107,40 +109,82 @@ void registerWaiting_exceedWaitingLimit() { when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); - Long userId = 10L; String publicCode = "ZiVXAD1vVr5b"; Store store = Store.builder().publicCode(publicCode).build(); User user = User.builder().id(userId).build(); - // when when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); when(customOAuth2User.getUserId()).thenReturn(10L); when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); - doThrow(new UserWaitingLimitExceededException()) + doNothing() .when(waitingRedisRepository) - .incrementAndCheckWaitingLimit(10L, 3L); + .incrementAndCheckWaitingLimit(userId, 3L); + + when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); + + // when + RegisterWaitingResponse response = waitingService.registerWaiting( + customOAuth2User, + publicCode, + request, + httpServletRequest + ); // then + assertThat(response).isNotNull(); + assertThat(response.getPartySize()).isEqualTo(4); + assertThat(response.getWaitingNumber()).isNotBlank(); + + verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); + verify(reservationRepository).save(any(Reservation.class)); + verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); + verify(waitingIdempotencyRepository).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + } + + @Test + @DisplayName("DB 저장 중 예외 발생 시 이벤트 발행 및 멱등 저장이 수행되지 않음") + void registerWaiting_dbSaveException() { + // given + when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.empty()); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + Store store = Store.builder().publicCode("ZiVXAD1vVr5b").build(); + User user = User.builder().id(10L).build(); + + when(storeRepository.findByPublicCodeAndDeletedFalse("ZiVXAD1vVr5b")) + .thenReturn(Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(10L); + when(userRepository.findById(10L)) + .thenReturn(Optional.of(user)); + + doNothing().when(waitingRedisRepository).incrementAndCheckWaitingLimit(10L, 3L); + + when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); + + doThrow(new RuntimeException("DB 저장 실패")) + .when(reservationRepository) + .save(any(Reservation.class)); + + + // when & then assertThatThrownBy(() -> waitingService.registerWaiting( customOAuth2User, "ZiVXAD1vVr5b", request, httpServletRequest - )).isInstanceOf(UserWaitingLimitExceededException.class); + )).isInstanceOf(RuntimeException.class); - verify(reservationRepository, never()).save(any(Reservation.class)); - verify(eventPublisher, never()).publishEvent(any()); + verify(eventPublisher, never()).publishEvent(any(AddWaitingRegisterEvent.class)); verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); - verify(waitingRedisRepository).incrementAndCheckWaitingLimit(10L, 3L); } - @Test - @DisplayName("웨이팅 정상 등록 시 DB 저장, 이벤트 발생, 멱등 응답 저장 수행") - void registerWaiting_success() { + @DisplayName("웨이팅 개수 제한 초과 시 예외 발생 테스트") + void registerWaiting_exceedWaitingLimit() { // given CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); RegisterWaitingRequest request = new RegisterWaitingRequest(4); @@ -149,38 +193,90 @@ void registerWaiting_success() { when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); + Long userId = 10L; String publicCode = "ZiVXAD1vVr5b"; Store store = Store.builder().publicCode(publicCode).build(); User user = User.builder().id(userId).build(); + // when when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); when(customOAuth2User.getUserId()).thenReturn(10L); when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); - doNothing() + doThrow(new UserWaitingLimitExceededException()) .when(waitingRedisRepository) - .incrementAndCheckWaitingLimit(userId, 3L); + .incrementAndCheckWaitingLimit(10L, 3L); - when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); + // then + assertThatThrownBy(() -> waitingService.registerWaiting( + customOAuth2User, + "ZiVXAD1vVr5b", + request, + httpServletRequest + )).isInstanceOf(UserWaitingLimitExceededException.class); + + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingRedisRepository).incrementAndCheckWaitingLimit(10L, 3L); + } + + @Test + @DisplayName("존재하지 않는 publicCode이면 StoreNotFoundException 발생") + void registerWaiting_storeNotFound() { + // given + when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.empty()); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); // when - RegisterWaitingResponse response = waitingService.registerWaiting( + when(storeRepository.findByPublicCodeAndDeletedFalse("invalidCode")) + .thenReturn(Optional.empty()); + + // then + assertThatThrownBy(() -> waitingService.registerWaiting( customOAuth2User, - publicCode, + "invalidCode", request, httpServletRequest - ); + )).isInstanceOf(StoreNotFoundException.class); + + verify(userRepository, never()).findById(anyLong()); + verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + } + + @Test + @DisplayName("존재하지 않는 userId이면 UserNotFoundException 발생") + void registerWaiting_userNotFound() { + // given + when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.empty()); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + String publicCode = "ZiVXAD1vVr5b"; + + Store store = Store.builder().publicCode(publicCode).build(); + + // when + when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)) + .thenReturn(Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(10L); + when(userRepository.findById(10L)) + .thenReturn(Optional.empty()); // then - assertThat(response).isNotNull(); - assertThat(response.getPartySize()).isEqualTo(4); - assertThat(response.getWaitingNumber()).isNotBlank(); + assertThatThrownBy(() -> waitingService.registerWaiting( + customOAuth2User, + publicCode, + request, + httpServletRequest + )).isInstanceOf(UserNotFoundException.class); - verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); - verify(reservationRepository).save(any(Reservation.class)); - verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); - verify(waitingIdempotencyRepository).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); } } From 9adb0c7ce7d00e5f65c73d9a2e2f5d0e947b5fc5 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 23:16:17 +0900 Subject: [PATCH 668/685] =?UTF-8?q?test:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=98=88=EC=99=B8=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index 2e407abc..0a0a9075 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -98,6 +98,50 @@ void registerWaiting_idempotentKeyExists() { verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); } + @Test + @DisplayName("Idempotency-Key가 blank이면 멱등 로직을 타지 않는다") + void registerWaiting_idempotentKeyNotExists() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(" "); + + Long userId = 10L; + String publicCode = "ZiVXAD1vVr5b"; + + Store store = Store.builder().publicCode(publicCode).build(); + User user = User.builder().id(userId).build(); + + when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(10L); + when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); + + doNothing() + .when(waitingRedisRepository) + .incrementAndCheckWaitingLimit(userId, 3L); + + when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); + + // when + RegisterWaitingResponse response = waitingService.registerWaiting( + customOAuth2User, + publicCode, + request, + httpServletRequest + ); + + // then + assertThat(response).isNotNull(); + assertThat(response.getPartySize()).isEqualTo(4); + assertThat(response.getWaitingNumber()).isNotBlank(); + + verify(waitingIdempotencyRepository, never()).findByKey(any()); + verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); + verify(reservationRepository).save(any(Reservation.class)); + verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); + } + @Test @DisplayName("웨이팅 정상 등록 시 DB 저장, 이벤트 발생, 멱등 응답 저장 수행") void registerWaiting_success() { From b4c0c4bf1a0e6dfc8b6f90413913f72200f3d524 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sat, 7 Feb 2026 23:19:02 +0900 Subject: [PATCH 669/685] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nowait/applicationuser/waiting/service/WaitingService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index cb2e7ff0..ef3d8e32 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; From 64ea4c67c8a9e6b9f25b51f3c2d8d41c08870cbb Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 13:53:27 +0900 Subject: [PATCH 670/685] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4?= =?UTF-8?q?=20=EC=97=86=EB=8A=94=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingService.java | 5 +-- .../waiting/service/WaitingServiceTest.java | 44 ------------------- 2 files changed, 1 insertion(+), 48 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index ef3d8e32..b92395ea 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -58,6 +58,7 @@ public class WaitingService { @Transactional public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, String publicCode, RegisterWaitingRequest waitingRequest, HttpServletRequest httpServletRequest) { + // TODO 멱등키 동시성 처리 로직 고려 필요 (분산락 등) RegisterWaitingResponse registerWaitingResponse = validateIdempotency(httpServletRequest); if (registerWaitingResponse != null) { log.info("Idempotent request detected. Returning existing response."); @@ -156,10 +157,6 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p private RegisterWaitingResponse validateIdempotency(HttpServletRequest httpServletRequest) { String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - if (idempotentKey == null || idempotentKey.isBlank()) { - return null; - } - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 // TODO 멱등성 검증 로직 점검 필요 return waitingIdempotencyRepository.findByKey(idempotentKey) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index 0a0a9075..2e407abc 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -98,50 +98,6 @@ void registerWaiting_idempotentKeyExists() { verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); } - @Test - @DisplayName("Idempotency-Key가 blank이면 멱등 로직을 타지 않는다") - void registerWaiting_idempotentKeyNotExists() { - // given - CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); - RegisterWaitingRequest request = new RegisterWaitingRequest(4); - - when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(" "); - - Long userId = 10L; - String publicCode = "ZiVXAD1vVr5b"; - - Store store = Store.builder().publicCode(publicCode).build(); - User user = User.builder().id(userId).build(); - - when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); - when(customOAuth2User.getUserId()).thenReturn(10L); - when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); - - doNothing() - .when(waitingRedisRepository) - .incrementAndCheckWaitingLimit(userId, 3L); - - when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); - - // when - RegisterWaitingResponse response = waitingService.registerWaiting( - customOAuth2User, - publicCode, - request, - httpServletRequest - ); - - // then - assertThat(response).isNotNull(); - assertThat(response.getPartySize()).isEqualTo(4); - assertThat(response.getWaitingNumber()).isNotBlank(); - - verify(waitingIdempotencyRepository, never()).findByKey(any()); - verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); - verify(reservationRepository).save(any(Reservation.class)); - verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); - } - @Test @DisplayName("웨이팅 정상 등록 시 DB 저장, 이벤트 발생, 멱등 응답 저장 수행") void registerWaiting_success() { From 8996da78cfe8982b7c5531617fe8d73352f717e2 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 15:11:20 +0900 Subject: [PATCH 671/685] =?UTF-8?q?test:=20=EB=A9=B1=EB=93=B1=ED=82=A4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index 2e407abc..cb5b8f84 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -98,6 +98,49 @@ void registerWaiting_idempotentKeyExists() { verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); } + @Test + @DisplayName("Idempotency-Key가 blank이면 멱등 로직을 타지 않는다") + void registerWaiting_idempotentKeyNotExists() { + // given + CustomOAuth2User customOAuth2User = mock(CustomOAuth2User.class); + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(" "); + + Long userId = 10L; + String publicCode = "ZiVXAD1vVr5b"; + + Store store = Store.builder().publicCode(publicCode).build(); + User user = User.builder().id(userId).build(); + + when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); + when(customOAuth2User.getUserId()).thenReturn(10L); + when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); + + doNothing() + .when(waitingRedisRepository) + .incrementAndCheckWaitingLimit(userId, 3L); + + when(waitingRedisRepository.incrementDailySequence(anyString())).thenReturn(1L); + + // when + RegisterWaitingResponse response = waitingService.registerWaiting( + customOAuth2User, + publicCode, + request, + httpServletRequest + ); + + // then + assertThat(response).isNotNull(); + assertThat(response.getPartySize()).isEqualTo(4); + assertThat(response.getWaitingNumber()).isNotBlank(); + + verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); + verify(reservationRepository).save(any(Reservation.class)); + verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); + } + @Test @DisplayName("웨이팅 정상 등록 시 DB 저장, 이벤트 발생, 멱등 응답 저장 수행") void registerWaiting_success() { From 3388353946fb43806e2674710260e930a2174d7b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 15:11:45 +0900 Subject: [PATCH 672/685] =?UTF-8?q?feat:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=A9=B1?= =?UTF-8?q?=EB=93=B1=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/controller/WaitingController.java | 6 ++-- .../dto/WaitingCancelIdempotencyValue.java | 13 +++++++ .../redis/WaitingIdempotencyRepository.java | 36 ++++++++++++++++++- .../waiting/service/WaitingService.java | 28 ++++++++++----- 4 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingCancelIdempotencyValue.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java index e49dda65..dbc2ed03 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java @@ -63,12 +63,14 @@ public ResponseEntity registerWaiting( public ResponseEntity cancelWaiting( @AuthenticationPrincipal CustomOAuth2User customOAuth2User, @PathVariable String publicCode, - @RequestBody CancelWaitingRequest request + @RequestBody CancelWaitingRequest request, + HttpServletRequest httpServletRequest ) { CancelWaitingResponse cancelWaitingResponse = waitingService.cancelWaiting( customOAuth2User, publicCode, - request + request, + httpServletRequest ); return ResponseEntity diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingCancelIdempotencyValue.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingCancelIdempotencyValue.java new file mode 100644 index 00000000..92bb2346 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/WaitingCancelIdempotencyValue.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class WaitingCancelIdempotencyValue { + private String state; + private CancelWaitingResponse response; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 50f4f1f4..3fe4a3d8 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -7,7 +7,9 @@ import org.springframework.stereotype.Repository; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import lombok.RequiredArgsConstructor; @@ -38,8 +40,25 @@ public Optional findByKey(String key) { } } + // 멱등키 조회 메서드 + public Optional findByCancelKey(String key) { + String idempotencyValue = redisTemplate.opsForValue().get(key); + + if (idempotencyValue == null) { + return Optional.empty(); + } + + try { + return Optional.of( + objectMapper.readValue(idempotencyValue, WaitingCancelIdempotencyValue.class) + ); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to deserialize value from Redis", e); + } + } - // 멱등키 저장 메서드 + + // 멱등키 저장 메서드 - 대기 등록 public void saveIdempotencyValue(String key, RegisterWaitingResponse response) { WaitingIdempotencyValue waitingIdempotencyValue = new WaitingIdempotencyValue( "COMPLETED", @@ -53,4 +72,19 @@ public void saveIdempotencyValue(String key, RegisterWaitingResponse response) { throw new IllegalArgumentException("Failed to serialize value for Redis", e); } } + + // 멱등키 저장 메서드 - 대기 취소 + public void saveCancelIdempotencyValue(String key, CancelWaitingResponse response) { + WaitingCancelIdempotencyValue waitingIdempotencyValue = new WaitingCancelIdempotencyValue( + "COMPLETED", + response + ); + + try { + String jsonValue = objectMapper.writeValueAsString(waitingIdempotencyValue); + redisTemplate.opsForValue().set(key, jsonValue, TTL); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to serialize value for Redis", e); + } + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index b92395ea..cfcd2476 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -12,6 +12,7 @@ import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; @@ -115,7 +116,13 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri } @Transactional - public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String publicCode, CancelWaitingRequest request) { + public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String publicCode, CancelWaitingRequest request, HttpServletRequest httpServletRequest) { + // TODO 멱등키 동시성 처리 로직 고려 필요 (분산락 등) + CancelWaitingResponse cancelWaitingResponse = validateCancelIdempotency(httpServletRequest); + if (cancelWaitingResponse != null) { + log.info("Idempotent request detected. Returning existing response."); + return cancelWaitingResponse; + } Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode).orElseThrow(StoreNotFoundException::new); Long storeId = store.getStoreId(); @@ -123,13 +130,6 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p User user = userRepository.findById(oAuth2User.getUserId()) .orElseThrow(UserNotFoundException::new); - // TODO 멱등키 검증 로직 점검 필요 - // Optional existingIdempotencyValue = waitingIdempotencyRepository.findByKey(idempotentKey); - // if (existingIdempotencyValue.isPresent()) { - // log.info("Existing idempotency key found: {}", idempotentKey); - // return existingIdempotencyValue.get().getResponse(); - // } - // DB 웨이팅 상태 취소 처리 Reservation reservation = reservationRepository.findReservationByReservationNumber(request.getWaitingNumber()) .orElseThrow(ReservationNotFoundException::new); @@ -148,7 +148,7 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .build(); // 멱등키가 있다면 멱등 응답 저장 - // waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + waitingIdempotencyRepository.saveCancelIdempotencyValue(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } @@ -164,6 +164,16 @@ private RegisterWaitingResponse validateIdempotency(HttpServletRequest httpServl .orElse(null); } + private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 + // TODO 멱등성 검증 로직 점검 필요 + return waitingIdempotencyRepository.findByCancelKey(idempotentKey) + .map(WaitingCancelIdempotencyValue::getResponse) + .orElse(null); + } + // 멱등키 응답 저장 메서드 private void saveIdempotencyResponse(String idempotentKey, RegisterWaitingResponse response) { if (idempotentKey != null && !idempotentKey.isBlank()) { From ffe7a1d6119b689b02174647b93a26ed41e2a26b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 16:48:03 +0900 Subject: [PATCH 673/685] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/dto/IdempotencyResponse.java | 13 ++++++++++ .../redis/WaitingIdempotencyRepository.java | 26 ++++--------------- .../waiting/service/WaitingService.java | 18 +++++++++---- .../waiting/service/WaitingServiceTest.java | 10 +++---- 4 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java new file mode 100644 index 00000000..00ccb5c9 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class IdempotencyResponse { + private String state; + private Object response; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 3fe4a3d8..41b03e3d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -7,8 +7,7 @@ import org.springframework.stereotype.Repository; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; -import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.IdempotencyResponse; import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; @@ -58,30 +57,15 @@ public Optional findByCancelKey(String key) { } - // 멱등키 저장 메서드 - 대기 등록 - public void saveIdempotencyValue(String key, RegisterWaitingResponse response) { - WaitingIdempotencyValue waitingIdempotencyValue = new WaitingIdempotencyValue( + // 멱등키 저장 메서드 + public void saveIdempotencyResponse(String key, Object response) { + IdempotencyResponse idempotencyResponse = new IdempotencyResponse( "COMPLETED", response ); try { - String jsonValue = objectMapper.writeValueAsString(waitingIdempotencyValue); - redisTemplate.opsForValue().set(key, jsonValue, TTL); - } catch (Exception e) { - throw new IllegalArgumentException("Failed to serialize value for Redis", e); - } - } - - // 멱등키 저장 메서드 - 대기 취소 - public void saveCancelIdempotencyValue(String key, CancelWaitingResponse response) { - WaitingCancelIdempotencyValue waitingIdempotencyValue = new WaitingCancelIdempotencyValue( - "COMPLETED", - response - ); - - try { - String jsonValue = objectMapper.writeValueAsString(waitingIdempotencyValue); + String jsonValue = objectMapper.writeValueAsString(idempotencyResponse); redisTemplate.opsForValue().set(key, jsonValue, TTL); } catch (Exception e) { throw new IllegalArgumentException("Failed to serialize value for Redis", e); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index cfcd2476..b3e25077 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -52,8 +53,6 @@ public class WaitingService { /** * 최초 대기 등록 - * @param publicCode - * @param waitingRequest */ // 대기열 리팩토링 서비스 메서드 @Transactional @@ -148,7 +147,7 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .build(); // 멱등키가 있다면 멱등 응답 저장 - waitingIdempotencyRepository.saveCancelIdempotencyValue(httpServletRequest.getHeader("Idempotency-Key"), response); + saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } @@ -174,10 +173,19 @@ private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpS .orElse(null); } + // TODO 공통 멱등키 검증 메서드로 리팩토링 필요 + private Optional validateIdempotency_(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 + // TODO 멱등성 검증 로직 점검 필요 + return waitingIdempotencyRepository.findByKey(idempotentKey); + } + // 멱등키 응답 저장 메서드 - private void saveIdempotencyResponse(String idempotentKey, RegisterWaitingResponse response) { + private void saveIdempotencyResponse(String idempotentKey, Object response) { if (idempotentKey != null && !idempotentKey.isBlank()) { - waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + waitingIdempotencyRepository.saveIdempotencyResponse(idempotentKey, response); } } diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index cb5b8f84..edc05f3a 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -95,7 +95,7 @@ void registerWaiting_idempotentKeyExists() { verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); verify(reservationRepository, never()).save(any(Reservation.class)); verify(eventPublisher, never()).publishEvent(any()); - verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } @Test @@ -114,7 +114,7 @@ void registerWaiting_idempotentKeyNotExists() { User user = User.builder().id(userId).build(); when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); - when(customOAuth2User.getUserId()).thenReturn(10L); + when(customOAuth2User.getUserId()).thenReturn(userId); when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); doNothing() @@ -184,7 +184,7 @@ void registerWaiting_success() { verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); verify(reservationRepository).save(any(Reservation.class)); verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); - verify(waitingIdempotencyRepository).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } @Test @@ -222,7 +222,7 @@ void registerWaiting_dbSaveException() { )).isInstanceOf(RuntimeException.class); verify(eventPublisher, never()).publishEvent(any(AddWaitingRegisterEvent.class)); - verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } @Test @@ -262,7 +262,7 @@ void registerWaiting_exceedWaitingLimit() { verify(reservationRepository, never()).save(any(Reservation.class)); verify(eventPublisher, never()).publishEvent(any()); - verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); verify(waitingRedisRepository).incrementAndCheckWaitingLimit(10L, 3L); } From 4d52fa53f77fdc37dfffacdca4d49185909d072c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 16:48:40 +0900 Subject: [PATCH 674/685] =?UTF-8?q?refactor:=20todo=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/waiting/service/WaitingService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index b3e25077..a4323900 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -173,12 +173,10 @@ private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpS .orElse(null); } - // TODO 공통 멱등키 검증 메서드로 리팩토링 필요 + // TODO : 공통 멱등키 검증 메서드로 리팩토링 필요!!!!!!!!! private Optional validateIdempotency_(HttpServletRequest httpServletRequest) { String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // TODO 멱등성 검증 로직 점검 필요 return waitingIdempotencyRepository.findByKey(idempotentKey); } From 7c23b1ff63687001116058dbed7ba1c7f2dfb0dc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:20:28 +0900 Subject: [PATCH 675/685] =?UTF-8?q?feat:=20=EB=A9=B1=EB=93=B1=ED=82=A4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EC=A4=91=20=EC=97=90=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 10 ++++++++++ .../waiting/exception/WorkInProgressException.java | 7 +++++++ .../java/com/nowait/common/exception/ErrorMessage.java | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 3eb701b7..96a95d95 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -21,6 +21,7 @@ import com.nowait.applicationuser.security.exception.ResourceNotFoundException; import com.nowait.applicationuser.security.exception.UnauthorizedException; +import com.nowait.applicationuser.waiting.exception.WorkInProgressException; import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; import com.nowait.discord.service.DiscordAlarmService; @@ -292,6 +293,15 @@ public ErrorResponse handleReservationAddUnauthorizedException( return new ErrorResponse(e.getMessage(), RESERVATION_ADD_UNAUTHORIZED.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(WorkInProgressException.class) + public ErrorResponse handleWorkInProgressException( + WorkInProgressException e, WebRequest request) { + alarm(e, request); + log.error("handleWorkInProgressException", e); + return new ErrorResponse(e.getMessage(), WORK_IN_PROGRESS.getCode()); + } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java new file mode 100644 index 00000000..8ea38bcd --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java @@ -0,0 +1,7 @@ +package com.nowait.applicationuser.waiting.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class WorkInProgressException extends RuntimeException { + public WorkInProgressException() { super(ErrorMessage.WORK_IN_PROGRESS.getMessage()); } +} diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index ba58c78a..6aa6267e 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -93,6 +93,9 @@ public enum ErrorMessage { // search SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"), + // idempotency + WORK_IN_PROGRESS("해당 요청이 이미 처리 중입니다. 잠시 후 다시 시도해주세요.", "idempotency001"), + // common UNEXPECTED_ERROR("예상하지 못한 오류가 발생했습니다.", "common999"); From 4177debce516e39c76bfc2d9d707de3db21a03ff Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:20:42 +0900 Subject: [PATCH 676/685] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/IdempotencyService.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java new file mode 100644 index 00000000..7c039bf8 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java @@ -0,0 +1,51 @@ +package com.nowait.applicationuser.waiting.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; +import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; +import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class IdempotencyService { + + private final WaitingIdempotencyRepository waitingIdempotencyRepository; + + // 멱등키 검증 메서드 + // TODO : 공통 멱등키 검증 메서드로 리팩토링 필요!!!!!!!!! + public Optional validateIdempotency(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + return waitingIdempotencyRepository.findByRegisterKey(idempotentKey); + } + + public WaitingCancelIdempotencyValue validateIdempotencyCancel(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + return waitingIdempotencyRepository.findByCancelKey(idempotentKey); + } + + // 멱등키 응답 저장 메서드 + public void saveIdempotencyResponse(String idempotentKey, Object response) { + if (idempotentKey != null && !idempotentKey.isBlank()) { + waitingIdempotencyRepository.saveIdempotencyResponse(idempotentKey, response); + } + } + + // 멱등키 PROCESSING 상태로 최초 저장 + public void saveIdempotencyKeyInProgress(String idempotentKey) { + if (idempotentKey != null && !idempotentKey.isBlank()) { + log.info("Saving idempotency key in progress: {}", idempotentKey); + waitingIdempotencyRepository.saveIdempotencyInProgress(idempotentKey); + } + } + +} From 3e2f978efdcdeef2dda4e3db80d30be3594856d3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:21:01 +0900 Subject: [PATCH 677/685] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/WaitingIdempotencyRepository.java | 32 +++++++--- .../waiting/service/WaitingService.java | 64 ++++++------------- 2 files changed, 42 insertions(+), 54 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 41b03e3d..505e1586 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -12,9 +12,11 @@ import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Repository @RequiredArgsConstructor +@Slf4j public class WaitingIdempotencyRepository { private final RedisTemplate redisTemplate; @@ -23,7 +25,7 @@ public class WaitingIdempotencyRepository { private static final Duration TTL = Duration.ofMinutes(10); // 멱등키 조회 메서드 - public Optional findByKey(String key) { + public Optional findByRegisterKey(String key) { String idempotencyValue = redisTemplate.opsForValue().get(key); if (idempotencyValue == null) { @@ -31,26 +33,24 @@ public Optional findByKey(String key) { } try { - return Optional.of( - objectMapper.readValue(idempotencyValue, WaitingIdempotencyValue.class) - ); + log.info("Idempotency value found in Redis for key {}: {}", key, idempotencyValue); + return Optional.of(objectMapper.readValue(idempotencyValue, WaitingIdempotencyValue.class)); } catch (Exception e) { throw new IllegalArgumentException("Failed to deserialize value from Redis", e); } } // 멱등키 조회 메서드 - public Optional findByCancelKey(String key) { + public WaitingCancelIdempotencyValue findByCancelKey(String key) { String idempotencyValue = redisTemplate.opsForValue().get(key); if (idempotencyValue == null) { - return Optional.empty(); + return null; } try { - return Optional.of( - objectMapper.readValue(idempotencyValue, WaitingCancelIdempotencyValue.class) - ); + log.info("Idempotency value found in Redis for key {}: {}", key, idempotencyValue); + return objectMapper.readValue(idempotencyValue, WaitingCancelIdempotencyValue.class); } catch (Exception e) { throw new IllegalArgumentException("Failed to deserialize value from Redis", e); } @@ -71,4 +71,18 @@ public void saveIdempotencyResponse(String key, Object response) { throw new IllegalArgumentException("Failed to serialize value for Redis", e); } } + + public void saveIdempotencyInProgress(String key) { + IdempotencyResponse idempotencyResponse = new IdempotencyResponse( + "IN-PROGRESS", + null + ); + + try { + String jsonValue = objectMapper.writeValueAsString(idempotencyResponse); + redisTemplate.opsForValue().set(key, jsonValue, TTL); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to serialize value for Redis", e); + } + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index a4323900..897c7cc1 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -16,7 +16,7 @@ import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; -import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; +import com.nowait.applicationuser.waiting.exception.WorkInProgressException; import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.exception.DepartmentNotFoundException; @@ -43,13 +43,13 @@ @Slf4j public class WaitingService { + private final IdempotencyService idempotencyService; private final ReservationRepository reservationRepository; private final WaitingRedisRepository waitingRedisRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; private final DepartmentRepository departmentRepository; private final ApplicationEventPublisher eventPublisher; - private final WaitingIdempotencyRepository waitingIdempotencyRepository; /** * 최초 대기 등록 @@ -59,10 +59,15 @@ public class WaitingService { public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, String publicCode, RegisterWaitingRequest waitingRequest, HttpServletRequest httpServletRequest) { // TODO 멱등키 동시성 처리 로직 고려 필요 (분산락 등) - RegisterWaitingResponse registerWaitingResponse = validateIdempotency(httpServletRequest); - if (registerWaitingResponse != null) { + Optional idempotencyResponse = idempotencyService.validateIdempotency(httpServletRequest); + if (idempotencyResponse.isPresent() && idempotencyResponse.getClass().equals("IN-PROGRESS")) { + throw new WorkInProgressException(); + } else if (idempotencyResponse.isPresent() && idempotencyResponse.getClass().equals("COMPLETED")) { log.info("Idempotent request detected. Returning existing response."); - return registerWaitingResponse; + return idempotencyResponse.get().getResponse(); + } else { + // TODO : DB 저장 실패 시 롤백 처리 필요 + idempotencyService.saveIdempotencyKeyInProgress(httpServletRequest.getHeader("Idempotency-Key")); } // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 @@ -109,7 +114,7 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri .build(); // TODO 멱등키 응답 실패 시 어떻게 처리할 지 점검 필요 - saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); + idempotencyService.saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } @@ -117,10 +122,14 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri @Transactional public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String publicCode, CancelWaitingRequest request, HttpServletRequest httpServletRequest) { // TODO 멱등키 동시성 처리 로직 고려 필요 (분산락 등) - CancelWaitingResponse cancelWaitingResponse = validateCancelIdempotency(httpServletRequest); - if (cancelWaitingResponse != null) { + WaitingCancelIdempotencyValue idempotencyResponse = idempotencyService.validateIdempotencyCancel(httpServletRequest); + if (idempotencyResponse != null && idempotencyResponse.getState().equals("IN-PROGRESS")) { + throw new WorkInProgressException(); + } else if (idempotencyResponse != null && idempotencyResponse.getState().equals("COMPLETED")) { log.info("Idempotent request detected. Returning existing response."); - return cancelWaitingResponse; + return idempotencyResponse.getResponse(); + } else { + idempotencyService.saveIdempotencyKeyInProgress(httpServletRequest.getHeader("Idempotency-Key")); } Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode).orElseThrow(StoreNotFoundException::new); @@ -147,46 +156,11 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .build(); // 멱등키가 있다면 멱등 응답 저장 - saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); + idempotencyService.saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } - // 멱등키 검증 메서드 - private RegisterWaitingResponse validateIdempotency(HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // TODO 멱등성 검증 로직 점검 필요 - return waitingIdempotencyRepository.findByKey(idempotentKey) - .map(WaitingIdempotencyValue::getResponse) - .orElse(null); - } - - private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // TODO 멱등성 검증 로직 점검 필요 - return waitingIdempotencyRepository.findByCancelKey(idempotentKey) - .map(WaitingCancelIdempotencyValue::getResponse) - .orElse(null); - } - - // TODO : 공통 멱등키 검증 메서드로 리팩토링 필요!!!!!!!!! - private Optional validateIdempotency_(HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - return waitingIdempotencyRepository.findByKey(idempotentKey); - } - - // 멱등키 응답 저장 메서드 - private void saveIdempotencyResponse(String idempotentKey, Object response) { - if (idempotentKey != null && !idempotentKey.isBlank()) { - waitingIdempotencyRepository.saveIdempotencyResponse(idempotentKey, response); - } - } - // 현재 대기 인원 수 조회 public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, String publicCode) { From 86d03aca531daa0222c78722ee70a607531aa5c3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:21:13 +0900 Subject: [PATCH 678/685] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index edc05f3a..afbd23b5 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -5,6 +5,7 @@ import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +21,7 @@ import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; @@ -38,6 +40,8 @@ class WaitingServiceTest { @InjectMocks private WaitingService waitingService; + @InjectMocks + private IdempotencyService idempotencyService; @Mock private ApplicationEventPublisher eventPublisher; @Mock @@ -73,7 +77,7 @@ void registerWaiting_idempotentKeyExists() { .partySize(4) .build(); - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.of(new WaitingIdempotencyValue( "COMPLETED", idempotentResponse @@ -98,6 +102,35 @@ void registerWaiting_idempotentKeyExists() { verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } + @Test + @DisplayName("멱등키가 In-PROGRESS 상태이면 DuplicateReservationException 발생") + void registerWaiting_idempotentKeyInProgress() { + // given + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.of(new WaitingIdempotencyValue( + "IN-PROGRESS", + null + ))); + + // when & then + assertThatThrownBy(() -> waitingService.registerWaiting( + customOAuth2User, + "ZiVXAD1vVr5b", + request, + httpServletRequest + )).isInstanceOf(DuplicateReservationException.class); + + verify(storeRepository, never()).findByPublicCodeAndDeletedFalse(anyString()); + verify(userRepository, never()).findById(anyLong()); + verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), + any(RegisterWaitingResponse.class)); + } + @Test @DisplayName("Idempotency-Key가 blank이면 멱등 로직을 타지 않는다") void registerWaiting_idempotentKeyNotExists() { @@ -149,7 +182,7 @@ void registerWaiting_success() { RegisterWaitingRequest request = new RegisterWaitingRequest(4); when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); Long userId = 10L; @@ -191,7 +224,7 @@ void registerWaiting_success() { @DisplayName("DB 저장 중 예외 발생 시 이벤트 발행 및 멱등 저장이 수행되지 않음") void registerWaiting_dbSaveException() { // given - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); RegisterWaitingRequest request = new RegisterWaitingRequest(4); @@ -233,7 +266,7 @@ void registerWaiting_exceedWaitingLimit() { RegisterWaitingRequest request = new RegisterWaitingRequest(4); when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); @@ -270,7 +303,7 @@ void registerWaiting_exceedWaitingLimit() { @DisplayName("존재하지 않는 publicCode이면 StoreNotFoundException 발생") void registerWaiting_storeNotFound() { // given - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); RegisterWaitingRequest request = new RegisterWaitingRequest(4); @@ -296,7 +329,7 @@ void registerWaiting_storeNotFound() { @DisplayName("존재하지 않는 userId이면 UserNotFoundException 발생") void registerWaiting_userNotFound() { // given - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); RegisterWaitingRequest request = new RegisterWaitingRequest(4); String publicCode = "ZiVXAD1vVr5b"; From d8132ac09e71e720d669a027c363bf8bceb7b85a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 18:50:44 +0900 Subject: [PATCH 679/685] =?UTF-8?q?refactor:=20=EC=9B=A8=EC=9D=B4=ED=8C=85?= =?UTF-8?q?=20=EC=A3=BC=EC=A0=90=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/controller/WaitingController.java | 21 +++++ .../waiting/dto/GetMyWaitingInfoResponse.java | 23 +++++ .../waiting/service/WaitingService.java | 35 ++++++++ .../reservation/dto/GetMyWaitingBaseDto.java | 49 +++++++++++ .../ReservationCustomRepository.java | 12 +++ .../ReservationCustomRepositoryImpl.java | 87 +++++++++++++++++++ .../repository/ReservationRepository.java | 2 +- 7 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java index dbc2ed03..52c3bc63 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java @@ -1,5 +1,7 @@ package com.nowait.applicationuser.waiting.controller; +import java.util.List; + import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -12,6 +14,7 @@ import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; +import com.nowait.applicationuser.waiting.dto.GetMyWaitingInfoResponse; import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; @@ -34,6 +37,24 @@ public class WaitingController { /** * 대기열 리팩토링용 API */ + @GetMapping() + @Operation(summary = "대기열 리팩토링용 API", description = "전체 대기 목록 조회") + public ResponseEntity getMyWaitingInfo( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User + ) { + List response = waitingService.getMyWaitingInfo( + customOAuth2User + ); + + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); + } + @PostMapping("/{publicCode}") @Operation(summary = "대기열 리팩토링용 API", description = "대기열 리팩토링용 API") public ResponseEntity registerWaiting( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java new file mode 100644 index 00000000..dd33aef1 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java @@ -0,0 +1,23 @@ +package com.nowait.applicationuser.waiting.dto; + +import java.time.LocalDateTime; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetMyWaitingInfoResponse { + private String reservationId; + private Long storeId; + private String storeName; + private String departmentName; + private Integer rank; + private Integer teamsAhead; + private Integer partySize; + private String status; + private LocalDateTime registeredAt; + private String location; + private String profileImageUrl; + private String bannerImageUrl; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index 897c7cc1..b7f7a3b2 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; @@ -10,6 +11,7 @@ import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; +import com.nowait.applicationuser.waiting.dto.GetMyWaitingInfoResponse; import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; @@ -21,6 +23,7 @@ import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.exception.DepartmentNotFoundException; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +import com.nowait.domaincorerdb.reservation.dto.GetMyWaitingBaseDto; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; @@ -161,6 +164,38 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p return response; } + // 웨이팅 목록 조회 + public List getMyWaitingInfo(CustomOAuth2User oAuth2User) { + User user = userRepository.findById(oAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + + Long userId = user.getId(); + + List waitingInfoList = reservationRepository.findMyWaitingInfo(userId); + + return waitingInfoList.stream() + .map(dto -> { + Long storeId = dto.getStoreId(); + Long rank = waitingRedisRepository.getWaitingCount(storeId); + + return GetMyWaitingInfoResponse.builder() + .reservationId(dto.getReservationId()) + .storeId(dto.getStoreId()) + .storeName(dto.getStoreName()) + .departmentName(dto.getDepartmentName()) + .rank(rank.intValue()) + .teamsAhead(rank.intValue() - 1) + .partySize(dto.getPartySize()) + .status(dto.getStatus().name()) + .registeredAt(dto.getRegisteredAt()) + .location(dto.getLocation()) + .profileImageUrl(dto.getProfileImageUrl()) + .bannerImageUrl(dto.getBannerImageUrl()) + .build(); + }) + .toList(); + } + // 현재 대기 인원 수 조회 public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, String publicCode) { diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java new file mode 100644 index 00000000..dc641dbf --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java @@ -0,0 +1,49 @@ +package com.nowait.domaincorerdb.reservation.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.nowait.common.enums.ReservationStatus; +import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class GetMyWaitingBaseDto { + private final String reservationId; + private final Long storeId; + private final String storeName; + private final String departmentName; + private final Integer partySize; + private final ReservationStatus status; + private final LocalDateTime registeredAt; + private final String location; + private final String profileImageUrl; + private final String bannerImageUrl; + + @QueryProjection + public GetMyWaitingBaseDto( + String reservationId, + Long storeId, + String storeName, + String departmentName, + Integer partySize, + ReservationStatus status, + LocalDateTime registeredAt, + String location, + String profileImageUrl, + String bannerImageUrl + ) { + this.reservationId = reservationId; + this.storeId = storeId; + this.storeName = storeName; + this.departmentName = departmentName; + this.partySize = partySize; + this.status = status; + this.registeredAt = registeredAt; + this.location = location; + this.profileImageUrl = profileImageUrl == null ? "" : profileImageUrl; + this.bannerImageUrl = bannerImageUrl == null ? "" : bannerImageUrl; + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java new file mode 100644 index 00000000..eb4014a6 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java @@ -0,0 +1,12 @@ +package com.nowait.domaincorerdb.reservation.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.nowait.domaincorerdb.reservation.dto.GetMyWaitingBaseDto; + +@Repository +public interface ReservationCustomRepository { + List findMyWaitingInfo(Long userId); +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java new file mode 100644 index 00000000..2138887a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java @@ -0,0 +1,87 @@ +package com.nowait.domaincorerdb.reservation.repository; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.department.entity.QDepartment; +import com.nowait.domaincorerdb.reservation.dto.GetMyWaitingBaseDto; +import com.nowait.domaincorerdb.reservation.dto.QGetMyWaitingBaseDto; +import com.nowait.domaincorerdb.reservation.entity.QReservation; +import com.nowait.domaincorerdb.store.entity.ImageType; +import com.nowait.domaincorerdb.store.entity.QStore; +import com.nowait.domaincorerdb.store.entity.QStoreImage; +import com.nowait.domaincorerdb.user.entity.QUser; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ReservationCustomRepositoryImpl implements ReservationCustomRepository { + + private final JPAQueryFactory queryFactory; + + private final QReservation reservation = QReservation.reservation; + private final QStore store = QStore.store; + private final QDepartment department = QDepartment.department; + private final QUser user = QUser.user; + private final QStoreImage storeImage = QStoreImage.storeImage; + + + @Override + public List findMyWaitingInfo(Long userId) { + + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); + + QStoreImage subStoreImage = new QStoreImage("subStoreImage"); + + return queryFactory + .select(new QGetMyWaitingBaseDto( + reservation.reservationNumber, + store.storeId, + store.name, + department.name, + reservation.partySize, + reservation.status, + reservation.requestedAt, + store.location, + storeImage.imageUrl, + + JPAExpressions + .select(subStoreImage.imageUrl) + .from(subStoreImage) + .where( + subStoreImage.store.storeId.eq(store.storeId), + subStoreImage.imageType.eq(ImageType.BANNER) + ) + .limit(1) + )) + .from(reservation) + .join(reservation.store, store) + .join(reservation.user, user) + .leftJoin(department) + .on(store.departmentId.eq(department.id)) + .leftJoin(storeImage) + .on( + storeImage.store.storeId.eq(store.storeId), + storeImage.imageType.eq(ImageType.PROFILE) + ) + .where( + reservation.user.id.eq(userId), + reservation.status.in( + ReservationStatus.WAITING, + ReservationStatus.CALLING + ), + reservation.requestedAt.between(startOfDay, endOfDay), + store.deleted.isFalse() + ) + .fetch(); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 79ed84d5..15bff08b 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -13,7 +13,7 @@ import com.nowait.domaincorerdb.user.entity.User; @Repository -public interface ReservationRepository extends JpaRepository { +public interface ReservationRepository extends JpaRepository, ReservationCustomRepository { List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses); From 821e4c59df6bb65661060a33ef6e69420c565246 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 19:45:07 +0900 Subject: [PATCH 680/685] =?UTF-8?q?refactor:=20publicCode=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/waiting/dto/GetMyWaitingInfoResponse.java | 1 + .../nowait/applicationuser/waiting/service/WaitingService.java | 1 + .../domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java | 3 +++ .../repository/ReservationCustomRepositoryImpl.java | 1 + 4 files changed, 6 insertions(+) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java index dd33aef1..653b5811 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java @@ -9,6 +9,7 @@ @Builder public class GetMyWaitingInfoResponse { private String reservationId; + private String publicCode; private Long storeId; private String storeName; private String departmentName; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index b7f7a3b2..9eb65e05 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -180,6 +180,7 @@ public List getMyWaitingInfo(CustomOAuth2User oAuth2Us return GetMyWaitingInfoResponse.builder() .reservationId(dto.getReservationId()) + .publicCode(dto.getPublicCode()) .storeId(dto.getStoreId()) .storeName(dto.getStoreName()) .departmentName(dto.getDepartmentName()) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java index dc641dbf..457f8ddb 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java @@ -12,6 +12,7 @@ @Getter public class GetMyWaitingBaseDto { private final String reservationId; + private final String publicCode; private final Long storeId; private final String storeName; private final String departmentName; @@ -25,6 +26,7 @@ public class GetMyWaitingBaseDto { @QueryProjection public GetMyWaitingBaseDto( String reservationId, + String publicCode, Long storeId, String storeName, String departmentName, @@ -36,6 +38,7 @@ public GetMyWaitingBaseDto( String bannerImageUrl ) { this.reservationId = reservationId; + this.publicCode = publicCode; this.storeId = storeId; this.storeName = storeName; this.departmentName = departmentName; diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java index 2138887a..9571a062 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java @@ -45,6 +45,7 @@ public List findMyWaitingInfo(Long userId) { return queryFactory .select(new QGetMyWaitingBaseDto( reservation.reservationNumber, + store.publicCode, store.storeId, store.name, department.name, From 6a7189ac643d013b4767ab8785d8bedd8dbca791 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 20:05:09 +0900 Subject: [PATCH 681/685] =?UTF-8?q?refactor:=20reservationId=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20waitingNumber=EB=A1=9C=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/dto/GetMyWaitingInfoResponse.java | 2 +- .../applicationuser/waiting/service/WaitingService.java | 2 +- .../domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java index 653b5811..d7eade38 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java @@ -8,7 +8,7 @@ @Getter @Builder public class GetMyWaitingInfoResponse { - private String reservationId; + private String waitingNumber; private String publicCode; private Long storeId; private String storeName; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index 9eb65e05..e517ce20 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -179,7 +179,7 @@ public List getMyWaitingInfo(CustomOAuth2User oAuth2Us Long rank = waitingRedisRepository.getWaitingCount(storeId); return GetMyWaitingInfoResponse.builder() - .reservationId(dto.getReservationId()) + .waitingNumber(dto.getWaitingNumber()) .publicCode(dto.getPublicCode()) .storeId(dto.getStoreId()) .storeName(dto.getStoreName()) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java index 457f8ddb..b677df74 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java @@ -11,7 +11,7 @@ @Getter public class GetMyWaitingBaseDto { - private final String reservationId; + private final String waitingNumber; private final String publicCode; private final Long storeId; private final String storeName; @@ -25,7 +25,7 @@ public class GetMyWaitingBaseDto { @QueryProjection public GetMyWaitingBaseDto( - String reservationId, + String waitingNumber, String publicCode, Long storeId, String storeName, @@ -37,7 +37,7 @@ public GetMyWaitingBaseDto( String profileImageUrl, String bannerImageUrl ) { - this.reservationId = reservationId; + this.waitingNumber = waitingNumber; this.publicCode = publicCode; this.storeId = storeId; this.storeName = storeName; From d20c593339fbce8ae5aa2cc48ea5e2b539ef76c7 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Mon, 23 Feb 2026 13:20:16 +0900 Subject: [PATCH 682/685] =?UTF-8?q?refactor:=20=20ReservationNotFoundExcep?= =?UTF-8?q?tion=20=ED=84=B0=EC=A7=80=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EB=A9=B1=EB=93=B1=ED=82=A4=20=EB=A1=A4=EB=B0=B1=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/WaitingIdempotencyRepository.java | 4 ++++ .../waiting/service/IdempotencyService.java | 7 +++++++ .../waiting/service/WaitingService.java | 21 ++++++++++++------- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 505e1586..2d191f2c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -85,4 +85,8 @@ public void saveIdempotencyInProgress(String key) { throw new IllegalArgumentException("Failed to serialize value for Redis", e); } } + + public void deleteByRegisterKey(String key) { + redisTemplate.delete(key); + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java index 7c039bf8..039d6bbc 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java @@ -48,4 +48,11 @@ public void saveIdempotencyKeyInProgress(String idempotentKey) { } } + public void rollbackIdempotencyKey(String idempotentKey) { + if (idempotentKey != null && !idempotentKey.isBlank()) { + log.info("Rolling back idempotency key: {}", idempotentKey); + waitingIdempotencyRepository.deleteByRegisterKey(idempotentKey); + } + } + } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index e517ce20..37cdbcdf 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -142,13 +142,20 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .orElseThrow(UserNotFoundException::new); // DB 웨이팅 상태 취소 처리 - Reservation reservation = reservationRepository.findReservationByReservationNumber(request.getWaitingNumber()) - .orElseThrow(ReservationNotFoundException::new); - - reservation.markAsCancelled(LocalDateTime.now()); - - // Redis 대기열 취소 이벤트 발행 - waitingRedisRepository.removeWaiting(storeId, user.getId()); + Reservation reservation; + try { + reservation = reservationRepository.findReservationByReservationNumber(request.getWaitingNumber()) + .orElseThrow(ReservationNotFoundException::new); + + reservation.markAsCancelled(LocalDateTime.now()); + + // Redis 대기열 취소 이벤트 발행 + waitingRedisRepository.removeWaiting(storeId, user.getId()); + } catch (RuntimeException e) { + // 롤백 처리 + idempotencyService.rollbackIdempotencyKey(httpServletRequest.getHeader("Idempotency-Key")); + throw e; + } CancelWaitingResponse response = CancelWaitingResponse.builder() .waitingNumber(reservation.getReservationNumber()) From e422545a3895506b3a4214eabaffaaf8978916ae Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 28 Oct 2026 01:40:12 +0900 Subject: [PATCH 683/685] =?UTF-8?q?refactor:=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20N+1=EC=9D=84=20LEFT=20JOIN=20=ED=95=9C=20?= =?UTF-8?q?=EB=B2=88=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EA=BE=B8=EA=B3=A0=20men?= =?UTF-8?q?us=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- nowait-domain/domain-core-rdb/build.gradle | 2 + .../domaincorerdb/menu/entity/Menu.java | 5 +- .../menu/repository/MenuRepository.java | 28 ++++++++ .../menu/repository/MenuRepositoryTest.java | 69 +++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 nowait-domain/domain-core-rdb/src/test/java/com/nowait/domaincorerdb/menu/repository/MenuRepositoryTest.java diff --git a/nowait-domain/domain-core-rdb/build.gradle b/nowait-domain/domain-core-rdb/build.gradle index 48bcad51..d277ef0c 100644 --- a/nowait-domain/domain-core-rdb/build.gradle +++ b/nowait-domain/domain-core-rdb/build.gradle @@ -50,4 +50,6 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly 'com.h2database:h2' } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java index c1297176..e4f3d6ab 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/entity/Menu.java @@ -9,6 +9,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Index; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -17,7 +18,9 @@ import lombok.experimental.SuperBuilder; @Entity -@Table(name = "menus") +// 주점별 메뉴 조회(WHERE store_id = ? AND deleted = ? ORDER BY sort_order)가 전체 스캔·filesort 없이 끝나게 한다 +@Table(name = "menus", indexes = @Index(name = "idx_menus_store_id_deleted_sort_order", + columnList = "store_id, deleted, sort_order")) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java index b785836c..8e07ebae 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/repository/MenuRepository.java @@ -1,12 +1,19 @@ package com.nowait.domaincorerdb.menu.repository; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.nowait.domaincorerdb.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.entity.MenuImage; @Repository public interface MenuRepository extends JpaRepository { @@ -14,4 +21,25 @@ public interface MenuRepository extends JpaRepository { List findAllByStoreIdAndDeletedFalseOrderBySortOrder(Long storeId); Optional findByStoreIdAndIdAndDeletedFalse(Long storeId, Long menuId); Optional findByIdAndDeletedFalse(Long menuId); + + // 이미지가 아직 없는 메뉴도 보여야 하므로 LEFT JOIN (행: [Menu, MenuImage 또는 null]) + @Query(value = """ + SELECT m, i + FROM Menu m + LEFT JOIN MenuImage i ON i.menu = m + WHERE m.storeId = :storeId + AND m.deleted = false + ORDER BY m.sortOrder, i.id + """) + List findAllWithImageRowsByStoreId(@Param("storeId") Long storeId); + + /** + * 주점의 메뉴와 이미지를 쿼리 한 번으로 가져온다 (메뉴마다 이미지를 따로 조회하던 N+1 대체) + * sortOrder 순서를 유지하고, 이미지가 없는 메뉴는 빈 목록이다 + */ + default Map> findAllWithImagesByStoreId(Long storeId) { + return findAllWithImageRowsByStoreId(storeId).stream() + .collect(Collectors.groupingBy(row -> (Menu)row[0], LinkedHashMap::new, + Collectors.flatMapping(row -> Stream.ofNullable((MenuImage)row[1]), Collectors.toList()))); + } } diff --git a/nowait-domain/domain-core-rdb/src/test/java/com/nowait/domaincorerdb/menu/repository/MenuRepositoryTest.java b/nowait-domain/domain-core-rdb/src/test/java/com/nowait/domaincorerdb/menu/repository/MenuRepositoryTest.java new file mode 100644 index 00000000..7f9b2a60 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/test/java/com/nowait/domaincorerdb/menu/repository/MenuRepositoryTest.java @@ -0,0 +1,69 @@ +package com.nowait.domaincorerdb.menu.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import org.hibernate.SessionFactory; +import org.hibernate.stat.Statistics; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import com.nowait.domaincorerdb.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.entity.MenuImage; + +@DataJpaTest(properties = "spring.jpa.properties.hibernate.generate_statistics=true") +class MenuRepositoryTest { + + @SpringBootConfiguration + @EntityScan(basePackageClasses = Menu.class) + @EnableJpaRepositories(basePackageClasses = MenuRepository.class) + static class Config { + } + + @Autowired + MenuRepository menuRepository; + + @Autowired + TestEntityManager em; + + @Test + void 메뉴와_이미지를_쿼리_한번에_메뉴별로_묶는다() { + Menu second = menu(1L, 2L, false); + Menu first = menu(1L, 1L, false); + Menu noImage = menu(1L, 3L, false); + menu(1L, 4L, true); + menu(2L, 1L, false); + MenuImage a = image(first); + MenuImage b = image(second); + MenuImage c = image(second); + em.flush(); + em.clear(); + Statistics stats = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class).getStatistics(); + stats.clear(); + + Map> result = menuRepository.findAllWithImagesByStoreId(1L); + + assertThat(stats.getPrepareStatementCount()).isEqualTo(1); + // sortOrder 순서, 삭제된 메뉴·다른 주점 제외, 이미지 없는 메뉴는 빈 목록 + assertThat(result.keySet()).extracting(Menu::getId) + .containsExactly(first.getId(), second.getId(), noImage.getId()); + assertThat(result.values()).extracting(images -> images.stream().map(MenuImage::getId).toList()) + .containsExactly(List.of(a.getId()), List.of(b.getId(), c.getId()), List.of()); + } + + private Menu menu(Long storeId, Long sortOrder, boolean deleted) { + return em.persist(Menu.builder().storeId(storeId).name("menu").isSoldOut(false).sortOrder(sortOrder) + .deleted(deleted).build()); + } + + private MenuImage image(Menu menu) { + return em.persist(MenuImage.builder().menu(menu).imageUrl("url").fileKey("key").build()); + } +} From 534de1e5742552a66ded064e4393aa06c04339a8 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 28 Oct 2026 01:40:12 +0900 Subject: [PATCH 684/685] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EB=A1=9C=EC=BB=AC=20=EC=BA=90=EC=8B=9C=20+=20Redis?= =?UTF-8?q?=20Pub/Sub=20=EB=AC=B4=ED=9A=A8=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../menu/service/MenuImageService.java | 7 ++ .../menu/service/MenuService.java | 26 ++++--- .../repository/DistributedCachedMenuDao.java | 42 +++++++++++ .../menu/service/MenuService.java | 44 ++--------- nowait-domain/domain-redis/build.gradle | 3 + .../cache/CacheInvalidationPublisher.java | 55 ++++++++++++++ .../common/cache/DistributedCachedDao.java | 75 +++++++++++++++++++ .../common/util/RedisKeyUtils.java | 7 ++ .../domaincoreredis/config/RedisConfig.java | 8 ++ .../cache/DistributedCachedDaoTest.java | 52 +++++++++++++ 10 files changed, 273 insertions(+), 46 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/repository/DistributedCachedMenuDao.java create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/CacheInvalidationPublisher.java create mode 100644 nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDao.java create mode 100644 nowait-domain/domain-redis/src/test/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDaoTest.java diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java index b3726fe8..2b23b597 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuImageService.java @@ -14,6 +14,8 @@ import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincoreredis.common.cache.CacheInvalidationPublisher; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import com.nowait.infraaws.aws.s3.S3Service; import lombok.RequiredArgsConstructor; @@ -25,6 +27,8 @@ public class MenuImageService { private final MenuRepository menuRepository; private final MenuImageRepository menuImageRepository; private final S3Service s3Service; + private final CacheInvalidationPublisher cacheInvalidationPublisher; + @Transactional public MenuImageUploadResponse save(Long menuId, MultipartFile file) { if (file == null || file.isEmpty()) { @@ -52,6 +56,7 @@ public MenuImageUploadResponse save(Long menuId, MultipartFile file) { .build(); menuImageRepository.save(menuImage); + cacheInvalidationPublisher.invalidate(RedisKeyUtils.buildMenuCacheChannel(), String.valueOf(menu.getStoreId())); // 응답 생성 return MenuImageUploadResponse.fromEntity(menuImage); @@ -64,5 +69,7 @@ public void delete(Long id) { s3Service.delete(menuImage.getFileKey()); menuImageRepository.delete(menuImage); + cacheInvalidationPublisher.invalidate(RedisKeyUtils.buildMenuCacheChannel(), + String.valueOf(menuImage.getMenu().getStoreId())); } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index f69dbfa2..e986f9a8 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -34,6 +34,8 @@ import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; +import com.nowait.domaincoreredis.common.cache.CacheInvalidationPublisher; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; import lombok.RequiredArgsConstructor; @@ -44,6 +46,7 @@ public class MenuService { private final MenuRepository menuRepository; private final MenuImageRepository menuImageRepository; private final UserRepository userRepository; + private final CacheInvalidationPublisher cacheInvalidationPublisher; @Transactional public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) { @@ -55,6 +58,7 @@ public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails me // 메뉴 생성 로직 Menu toSave = request.toEntity(); Menu saved = menuRepository.save(toSave); + invalidateMenuCache(saved.getStoreId()); return MenuCreateResponse.fromEntity(saved); } @@ -66,16 +70,9 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberD // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 validateViewAuthorization(user, storeId); - List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); - - List menuReadResponse = menus.stream() - .map(menu -> { - List images = menuImageRepository.findByMenu(menu); - List imageDto = images.stream() - .map(MenuImageUploadResponse::fromEntity) - .toList(); - return MenuReadDto.fromEntity(menu, imageDto); - }) + List menuReadResponse = menuRepository.findAllWithImagesByStoreId(storeId).entrySet().stream() + .map(e -> MenuReadDto.fromEntity(e.getKey(), + e.getValue().stream().map(MenuImageUploadResponse::fromEntity).toList())) .toList(); return MenuReadResponse.of(menuReadResponse); @@ -116,6 +113,7 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDeta ); Menu saved = menuRepository.save(menu); + invalidateMenuCache(saved.getStoreId()); List images = menuImageRepository.findByMenu(saved); List imageDto = images.stream() @@ -163,6 +161,7 @@ public String updateMenuSortOrder(List requests, MemberDe menus.forEach(m -> m.updateSortOrder(idToSort.get(m.getId()))); menuRepository.saveAll(menus); + invalidateMenuCache(storeId); return "메뉴 순서가 성공적으로 업데이트되었습니다."; } @@ -175,6 +174,7 @@ public String deleteMenu(Long menuId, MemberDetails memberDetails) { validateDeleteAuthorization(user, menu.getStoreId()); menu.markAsDeleted(); menuRepository.save(menu); + invalidateMenuCache(menu.getStoreId()); return "Menu with ID " + menuId + " 삭제되었습니다."; } @@ -186,10 +186,16 @@ public Boolean toggleSoldOut(Long menuId) { menu.toggleSoldOut(); menuRepository.save(menu); + invalidateMenuCache(menu.getStoreId()); return menu.getIsSoldOut(); } + // user-api 의 DistributedCachedMenuDao 무효화 (커밋 후 발행) + private void invalidateMenuCache(Long storeId) { + cacheInvalidationPublisher.invalidate(RedisKeyUtils.buildMenuCacheChannel(), String.valueOf(storeId)); + } + private void validateViewAuthorization(User user, Long storeId) { if (!(Role.SUPER_ADMIN.equals(user.getRole()) || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/repository/DistributedCachedMenuDao.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/repository/DistributedCachedMenuDao.java new file mode 100644 index 00000000..133e9fa2 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/repository/DistributedCachedMenuDao.java @@ -0,0 +1,42 @@ +package com.nowait.applicationuser.menu.repository; + +import java.time.Duration; +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.stereotype.Component; + +import com.nowait.applicationuser.menu.dto.MenuImageUploadResponse; +import com.nowait.applicationuser.menu.dto.MenuReadDto; +import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincoreredis.common.cache.CacheInvalidationPublisher; +import com.nowait.domaincoreredis.common.cache.DistributedCachedDao; +import com.nowait.domaincoreredis.common.util.RedisKeyUtils; + +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 주점별 메뉴 목록 캐시 (key: storeId) + * 메뉴/메뉴 이미지 변경은 admin-api 가 커밋 후 storeId 로 무효화 메시지를 발행한다 + */ +@Component +public class DistributedCachedMenuDao extends DistributedCachedDao> { + + private final MenuRepository menuRepository; + + // ttl 을 0s 로 주면 매 조회가 DB 로 간다 (캐시 끄기: 비교 측정, 장애 시 차단) + public DistributedCachedMenuDao(MenuRepository menuRepository, @Value("${nowait.cache.menu.ttl:1d}") Duration ttl, + RedisMessageListenerContainer container, CacheInvalidationPublisher publisher, MeterRegistry meterRegistry) { + super(RedisKeyUtils.buildMenuCacheChannel(), ttl, 1_000, container, publisher, meterRegistry); + this.menuRepository = menuRepository; + } + + @Override + protected List load(String storeId) { + return menuRepository.findAllWithImagesByStoreId(Long.valueOf(storeId)).entrySet().stream() + .map(e -> MenuReadDto.fromEntity(e.getKey(), + e.getValue().stream().map(MenuImageUploadResponse::fromEntity).toList())) + .toList(); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java index 45aa92e5..3701598d 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/menu/service/MenuService.java @@ -1,19 +1,12 @@ package com.nowait.applicationuser.menu.service; -import java.util.List; - import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationuser.menu.dto.MenuImageUploadResponse; import com.nowait.applicationuser.menu.dto.MenuReadDto; import com.nowait.applicationuser.menu.dto.MenuReadResponse; -import com.nowait.domaincorerdb.menu.entity.Menu; -import com.nowait.domaincorerdb.menu.entity.MenuImage; +import com.nowait.applicationuser.menu.repository.DistributedCachedMenuDao; import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; -import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; -import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; @@ -24,12 +17,11 @@ @RequiredArgsConstructor public class MenuService { - private final MenuRepository menuRepository; - private final MenuImageRepository menuImageRepository; private final StoreRepository storeRepository; + private final DistributedCachedMenuDao menuDao; - - @Transactional(readOnly = true) + // @Transactional 을 걸지 않는다: 주점 조회로 잡힌 스냅샷 안에서 캐시 미스 로딩이 일어나면 + // 무효화 직후에도 커밋 전 메뉴가 TTL 동안 캐싱될 수 있다 public MenuReadResponse getAllMenusByStoreId(String publicCode) { if (publicCode == null) { throw new MenuParamEmptyException(); @@ -38,24 +30,9 @@ public MenuReadResponse getAllMenusByStoreId(String publicCode) { Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); - String storeName = store.getName(); - Long storeId = store.getStoreId(); - List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); - - List menuReadResponse = menus.stream() - .map(menu -> { - List images = menuImageRepository.findByMenu(menu); - List imageDto = images.stream() - .map(MenuImageUploadResponse::fromEntity) - .toList(); - return MenuReadDto.fromEntity(menu, imageDto); - }) - .toList(); - - return MenuReadResponse.of(storeName, menuReadResponse); + return MenuReadResponse.of(store.getName(), menuDao.get(String.valueOf(store.getStoreId()))); } - @Transactional(readOnly = true) public MenuReadDto getMenuById(String publicCode, Long menuId) { if (publicCode == null || menuId == null) { throw new MenuParamEmptyException(); @@ -64,14 +41,9 @@ public MenuReadDto getMenuById(String publicCode, Long menuId) { Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) .orElseThrow(StoreNotFoundException::new); - Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(store.getStoreId(), menuId) + return menuDao.get(String.valueOf(store.getStoreId())).stream() + .filter(menu -> menu.getMenuId().equals(menuId)) + .findFirst() .orElseThrow(MenuNotFoundException::new); - - List images = menuImageRepository.findByMenu(menu); - List imageDto = images.stream() - .map(MenuImageUploadResponse::fromEntity) - .toList(); - - return MenuReadDto.fromEntity(menu, imageDto); } } diff --git a/nowait-domain/domain-redis/build.gradle b/nowait-domain/domain-redis/build.gradle index 2beb40e2..3c03aef4 100644 --- a/nowait-domain/domain-redis/build.gradle +++ b/nowait-domain/domain-redis/build.gradle @@ -25,6 +25,8 @@ dependencies { implementation project(':nowait-event') implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'com.github.ben-manes.caffeine:caffeine' + implementation 'io.micrometer:micrometer-core' // SWAGGER implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' @@ -34,4 +36,5 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/CacheInvalidationPublisher.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/CacheInvalidationPublisher.java new file mode 100644 index 00000000..59855a7c --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/CacheInvalidationPublisher.java @@ -0,0 +1,55 @@ +package com.nowait.domaincoreredis.common.cache; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 캐시 무효화 메시지 발행 (INVALIDATE-{key}, CLEAR) + * 구독 측은 {@link DistributedCachedDao} + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class CacheInvalidationPublisher { + + public static final String INVALIDATE_PREFIX = "INVALIDATE-"; + public static final String CLEAR = "CLEAR"; + + private final StringRedisTemplate redisTemplate; + + public void invalidate(String channel, String key) { + publishAfterCommit(channel, INVALIDATE_PREFIX + key); + } + + public void clear(String channel) { + publishAfterCommit(channel, CLEAR); + } + + // 커밋 전에 발행하면 구독 측이 evict 직후 커밋 전 값을 다시 읽어 TTL 동안 캐싱한다 + private void publishAfterCommit(String channel, String message) { + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + publish(channel, message); + return; + } + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + publish(channel, message); + } + }); + } + + private void publish(String channel, String message) { + try { + redisTemplate.convertAndSend(channel, message); + } catch (Exception e) { + // 발행 실패 시 TTL 만료 전까지 옛 값이 남는다 → redis-cli PUBLISH {channel} CLEAR 로 수동 무효화 + log.error("캐시 무효화 발행 실패 channel={}, message={}", channel, message, e); + } + } +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDao.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDao.java new file mode 100644 index 00000000..d87287b0 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDao.java @@ -0,0 +1,75 @@ +package com.nowait.domaincoreredis.common.cache; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.connection.SubscriptionListener; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics; + +/** + * 로컬 캐시(Caffeine) + Redis Pub/Sub 키 무효화 전파 + * - 값은 공유하지 않고 키만 전파한다 (직렬화 불필요, 결과적 일관성) + * - put 은 의도적으로 제공하지 않는다. 갱신은 invalidate 후 get 으로 재로딩 + * - 키는 메시지에 그대로 싣기 위해 String 으로 제한한다 + */ +public abstract class DistributedCachedDao implements MessageListener, SubscriptionListener { + + private final Cache cache; + private final String channel; + private final CacheInvalidationPublisher publisher; + + protected DistributedCachedDao(String channel, Duration ttl, long maximumSize, + RedisMessageListenerContainer container, CacheInvalidationPublisher publisher, MeterRegistry meterRegistry) { + this.cache = Caffeine.newBuilder() + .expireAfterWrite(ttl) + .maximumSize(maximumSize) + .recordStats() + .build(); + this.channel = channel; + this.publisher = publisher; + // 히트율: cache_gets_total{cache=, result=hit|miss} + CaffeineCacheMetrics.monitor(meterRegistry, cache, channel); + container.addMessageListener(this, new ChannelTopic(channel)); + } + + protected abstract V load(String key); + + public V get(String key) { + return cache.get(key, this::load); + } + + public void invalidate(String key) { + cache.invalidate(key); + publisher.invalidate(channel, key); + } + + public void clearCache() { + cache.invalidateAll(); + publisher.clear(channel); + } + + @Override + public void onMessage(Message message, byte[] pattern) { + String body = new String(message.getBody(), StandardCharsets.UTF_8); + if (CacheInvalidationPublisher.CLEAR.equals(body)) { + cache.invalidateAll(); + } else if (body.startsWith(CacheInvalidationPublisher.INVALIDATE_PREFIX)) { + cache.invalidate(body.substring(CacheInvalidationPublisher.INVALIDATE_PREFIX.length())); + } + } + + // 최초 구독 + 연결 끊김 후 재구독 시 호출된다 → 끊긴 동안 놓친 무효화를 전체 비우기로 수습 + @Override + public void onChannelSubscribed(byte[] channel, long count) { + cache.invalidateAll(); + } +} diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index 25467ea8..adad3e11 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -23,6 +23,13 @@ public class RedisKeyUtils { private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:"; private static final String WAITING_STATUS_KEY_PREFIX = "waiting:status:"; + // Cache invalidation channels + private static final String MENU_CACHE_CHANNEL = "cache:menus"; + + public static String buildMenuCacheChannel() { + return MENU_CACHE_CHANNEL; + } + // Waiting User keys public static String buildUserHoldingKey(String userId) { return "waiting:user:{" + userId + "}:holding"; // ZSET(member=token, score=expireEpochMs) diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java index b0967e0f..07dd0147 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java @@ -7,6 +7,7 @@ import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @@ -45,4 +46,11 @@ public RedisTemplate redisTemplate() { return redisTemplate; } + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer() { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(redisConnectionFactory()); + return container; + } } diff --git a/nowait-domain/domain-redis/src/test/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDaoTest.java b/nowait-domain/domain-redis/src/test/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDaoTest.java new file mode 100644 index 00000000..c0f78ace --- /dev/null +++ b/nowait-domain/domain-redis/src/test/java/com/nowait/domaincoreredis/common/cache/DistributedCachedDaoTest.java @@ -0,0 +1,52 @@ +package com.nowait.domaincoreredis.common.cache; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.DefaultMessage; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; + +class DistributedCachedDaoTest { + + static class CountingDao extends DistributedCachedDao { + int loads; + + CountingDao() { + super("cache:test", Duration.ofDays(1), 100, new RedisMessageListenerContainer(), null, + new SimpleMeterRegistry()); + } + + @Override + protected Integer load(String key) { + return ++loads; + } + } + + private static void receive(CountingDao dao, String body) { + dao.onMessage(new DefaultMessage("cache:test".getBytes(StandardCharsets.UTF_8), + body.getBytes(StandardCharsets.UTF_8)), null); + } + + @Test + void 무효화_메시지를_받으면_해당_키만_다시_로딩한다() { + CountingDao dao = new CountingDao(); + assertEquals(1, dao.get("1")); + assertEquals(2, dao.get("2")); + assertEquals(1, dao.get("1")); + + receive(dao, "INVALIDATE-1"); + assertEquals(3, dao.get("1")); + assertEquals(2, dao.get("2")); + + receive(dao, "CLEAR"); + assertEquals(4, dao.get("2")); + + dao.onChannelSubscribed("cache:test".getBytes(StandardCharsets.UTF_8), 1); + assertEquals(5, dao.get("2")); + } +} From 618347c89f8575ea3f62194e0d3f3442d031b5df Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Wed, 28 Oct 2026 01:40:12 +0900 Subject: [PATCH 685/685] =?UTF-8?q?docs:=20=EB=A9=94=EB=89=B4=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=20=EC=84=B1=EB=8A=A5=20=EC=B8=A1=EC=A0=95=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=EC=99=80=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- performance/menu-cache/01-join.md | 110 +++++++++ performance/menu-cache/02-index.md | 85 +++++++ performance/menu-cache/03-cache.md | 148 ++++++++++++ performance/menu-cache/README.md | 118 ++++++++++ performance/menu-cache/digest.sql | 14 ++ performance/menu-cache/menu-load.js | 59 +++++ performance/menu-cache/probe.sh | 17 ++ .../menu-cache/results/2026-10-26-ec2.md | 149 +++++++++++++ .../menu-cache/results/2026-10-26-mac.md | 79 +++++++ performance/menu-cache/run.sh | 210 ++++++++++++++++++ performance/menu-cache/seed.sql | 25 +++ performance/menu-cache/verify.sh | 23 ++ 12 files changed, 1037 insertions(+) create mode 100644 performance/menu-cache/01-join.md create mode 100644 performance/menu-cache/02-index.md create mode 100644 performance/menu-cache/03-cache.md create mode 100644 performance/menu-cache/README.md create mode 100644 performance/menu-cache/digest.sql create mode 100644 performance/menu-cache/menu-load.js create mode 100755 performance/menu-cache/probe.sh create mode 100644 performance/menu-cache/results/2026-10-26-ec2.md create mode 100644 performance/menu-cache/results/2026-10-26-mac.md create mode 100755 performance/menu-cache/run.sh create mode 100644 performance/menu-cache/seed.sql create mode 100644 performance/menu-cache/verify.sh diff --git a/performance/menu-cache/01-join.md b/performance/menu-cache/01-join.md new file mode 100644 index 00000000..e0c3ca73 --- /dev/null +++ b/performance/menu-cache/01-join.md @@ -0,0 +1,110 @@ +# 1단계: 메뉴 목록 조회의 N+1 을 JOIN 한 번으로 + +실행 원본: [EC2](results/2026-10-26-ec2.md), [Mac](results/2026-10-26-mac.md) + +## 1. 질문 + +기존 손님 메뉴 목록 조회(user-api `MenuService.getAllMenusByStoreId`, 커밋 `d20c593`)는 메뉴마다 이미지를 따로 조회했다. admin-api 의 같은 이름 메서드도 같은 모양이다. + +```java +List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); // 1번 +menus.stream().map(menu -> { + List images = menuImageRepository.findByMenu(menu); // 메뉴마다 1번 + ... +``` + +- 요청당 쿼리 = 주점 1 + 메뉴 1 + 이미지 N. 메뉴 20개면 22개 +- JPA 지연 로딩 N+1 이 아니라, 리포지토리를 루프에서 부르는 N+1 이다 +- 연관관계가 `MenuImage → Menu` 단방향 `@ManyToOne` 뿐이라 `Menu` 쪽에서 이미지를 fetch join 할 수 없다 + +질문 +- JOIN 한 번으로 바꿔도 응답이 기존과 같은가? (이미지 없는 메뉴, 이미지 여러 장, 정렬) +- 쿼리가 22개 → 2개가 되면 MySQL 한계가 얼마나 오르는가? + +## 2. 바꾼 것 + +`MenuRepository` 에 JOIN 쿼리와, 결과 행을 메뉴별로 묶는 default 메서드를 넣었다. user(`DistributedCachedMenuDao.load`)와 admin(`MenuService.getAllMenusByStoreId`) 둘 다 이것을 쓴다. + +```java +// 이미지가 아직 없는 메뉴도 보여야 하므로 LEFT JOIN (행: [Menu, MenuImage 또는 null]) +@Query(value = """ + SELECT m, i + FROM Menu m + LEFT JOIN MenuImage i ON i.menu = m + WHERE m.storeId = :storeId + AND m.deleted = false + ORDER BY m.sortOrder, i.id + """) +List findAllWithImageRowsByStoreId(@Param("storeId") Long storeId); + +default Map> findAllWithImagesByStoreId(Long storeId) { + return findAllWithImageRowsByStoreId(storeId).stream() + .collect(Collectors.groupingBy(row -> (Menu)row[0], LinkedHashMap::new, + Collectors.flatMapping(row -> Stream.ofNullable((MenuImage)row[1]), Collectors.toList()))); +} +``` + +- INNER JOIN 이 아니라 LEFT JOIN: admin 에서 메뉴 생성과 이미지 업로드가 따로라, 이미지가 아직 없는 메뉴가 있을 수 있다. INNER 면 손님 목록에서 사라진다 +- `LinkedHashMap` 으로 `sortOrder` 순서를 지키고, 이미지가 없는 메뉴는 빈 목록이 된다 +- `ORDER BY i.id`: 기존 `findByMenu` 가 돌려주던 이미지 순서(id 순)와 맞춘다 + +``` +기준 주점 SELECT → 메뉴 SELECT → 이미지 SELECT × 20 (+ 트랜잭션 설정 문장 약 5개) +1단계 주점 SELECT → 메뉴 LEFT JOIN 이미지 SELECT 1번 +``` + +이 단계의 user-api(`user-tobe.jar`)는 조회 트랜잭션도 없다(3단계에서 넣은 변경, [3단계 2절](03-cache.md#2-바꾼-것)). 기준 코드는 `@Transactional(readOnly = true)` 라 요청마다 `SET autocommit`, `SET SESSION TRANSACTION READ ONLY / READ WRITE`, `COMMIT` 이 더 나갔다. 이 단계의 차이에는 그것도 섞여 있다. 크기는 4절에서 나눠 본다. + +## 3. 실행 명령 + +```bash +performance/menu-cache/run.sh verify +RATES="100 200 400" performance/menu-cache/run.sh bench nplus1 +RATES="200 500 1000" performance/menu-cache/run.sh bench join # 캐시 끔(ttl 0s), 인덱스 지운 상태 +``` + +## 4. 관찰 결과 + +**응답은 기존과 같다.** 기존 jar 와 바꾼 jar 를 동시에 띄워 200개 주점 응답을 비교했다. user·admin 모두 200/200 같았다(EC2, Mac 둘 다). admin 목록 200회의 메뉴 관련 쿼리는 4,200(menus 200 + menu_images 4,000) → 200 이다. + +**요청당 쿼리 시간** (EC2, `performance_schema` 의 누적 실행 시간 ÷ 요청 수. 기준은 100 req/s, 1단계는 200 req/s) + +| 문장 | 기준 N+1 | 1단계 JOIN | +|---|---:|---:| +| 이미지 SELECT × 20 (`menu_images`) | 2.02ms | - | +| 메뉴 SELECT / 메뉴·이미지 JOIN (`menus`) | 1.88ms | 2.09ms | +| 주점 SELECT (`stores`) | 0.12ms | 0.14ms | +| 그 밖의 문장 (트랜잭션 설정 등) | 0.23ms | 0.03ms (admin 쪽) | +| 합계 | 4.25ms | 2.26ms | + +- 줄어든 약 2ms 의 대부분은 이미지 SELECT 20번(2.02ms)이다. 트랜잭션 설정 문장(요청당 약 5개)은 0.2ms 정도였다 +- `menus` 는 두 단계 모두 요청마다 약 4,000행을 읽었다(메뉴 테이블 전체 스캔). 이것이 2단계의 주제다 + +**같은 부하에서 (EC2)** + +| 목표 req/s | 단계 | 처리 req/s | p50 | p95 | p99 | 못 보낸 요청 | 요청당 쿼리 | MySQL CPU | user-api CPU | +|---:|---|---:|---:|---:|---:|---:|---:|---:|---:| +| 100 | N+1 | 100 | 9.3ms | 10.0ms | 11.4ms | 0 | 22.01 | 0.47 | 0.48 | +| 200 | N+1 | 187 | 1,492ms | 2,502ms | 3,157ms | 285 | 22.00 | 0.99 | 0.84 | +| 400 | N+1 | 187 | 10,625ms | 10,749ms | 11,704ms | 10,778 | 22.00 | 0.99 | 0.80 | +| 200 | JOIN | 200 | 3.5ms | 4.2ms | 4.7ms | 0 | 2.01 | 0.46 | 0.43 | +| 500 | JOIN | 499 | 74.2ms | 129.5ms | 160.6ms | 0 | 1.84 | 0.99 | 0.54 | +| 1000 | JOIN | 658 | 2,893ms | 3,170ms | 3,323ms | 18,517 | 1.62 | 0.99 | 0.66 | + +- N+1 은 200 req/s 에서 이미 무너졌다. MySQL 이 1코어를 다 쓰고 187 req/s 에서 멈췄다 +- JOIN 은 200 req/s 에서 MySQL 0.46코어로 여유가 있고, 500 에서 1코어가 찼다 +- p95 200ms 안에서 처리한 최대 부하: 100 → 500 +- JOIN 500·1000 은 캐시를 껐는데도 히트율이 16%·38% 로 찍혔다. TTL 0s 라도 같은 주점에 동시에 온 요청은 로딩 한 번을 나눠 받기 때문이다(Caffeine 키 단위 로딩, [3단계 4절](03-cache.md#4-관찰-결과)). 과부하 구간의 JOIN 수치는 그만큼 유리하다 + +Mac 에서도 순서는 같았다. N+1 은 200 req/s 를 넣어도 116 req/s, JOIN 은 200 req/s 를 p95 7.4ms 로 처리했다. + +## 5. 보장 범위와 해결하지 못한 문제 + +- 응답이 같다는 비교는 시드 데이터(이미지 0·1·2장) 기준이다. 삭제된 메뉴 제외는 `MenuRepositoryTest` 로만 확인했다 +- 이미지 순서를 위해 `ORDER BY i.id` 를 넣어 실행 계획에 `Using temporary; Using filesort` 가 남는다. 20행 정렬이라 무시했다 +- 이 단계의 차이에는 조회 트랜잭션 제거가 섞여 있다. 둘을 나눠 재지 않고, 쿼리 시간으로 크기만 나눠 봤다 +- 결과를 `Object[]` 행으로 받아 Java 에서 묶는다. `Menu` 에 `@OneToMany images` 를 두고 fetch join 하는 방법은 두 앱이 함께 쓰는 엔티티를 바꿔야 해서 택하지 않았다 + +## 6. 다음에 볼 것 + +- 남은 요청당 약 2ms 의 대부분이 `menus` 전체 스캔이다 → 2단계 인덱스 diff --git a/performance/menu-cache/02-index.md b/performance/menu-cache/02-index.md new file mode 100644 index 00000000..9e3a4ac3 --- /dev/null +++ b/performance/menu-cache/02-index.md @@ -0,0 +1,85 @@ +# 2단계: menus 에 (store_id, deleted, sort_order) 인덱스 + +실행 원본: [EC2](results/2026-10-26-ec2.md), [Mac](results/2026-10-26-mac.md) + +## 1. 질문 + +1단계 JOIN 쿼리는 요청마다 `menus` 를 약 4,000행 읽었다. `menus.store_id` 에 인덱스가 없었기 때문이다. + +- `Menu.storeId` 는 연관관계가 아니라 `Long` 컬럼이라 FK 가 없고, InnoDB 가 FK 때문에 자동으로 만드는 인덱스도 없다 +- 다른 쪽은 이미 인덱스가 있다: `menu_images.menu_id` 는 FK 인덱스, `stores.public_code` 는 unique 인덱스 + +질문 +- 인덱스로 요청당 읽는 행과 MySQL CPU 가 얼마나 줄고, 한계가 얼마나 오르는가? +- 운영 DB 에 별도 마이그레이션 없이 만들 수 있는가? + +## 2. 바꾼 것 + +```java +// 주점별 메뉴 조회(WHERE store_id = ? AND deleted = ? ORDER BY sort_order)가 전체 스캔·filesort 없이 끝나게 한다 +@Table(name = "menus", indexes = @Index(name = "idx_menus_store_id_deleted_sort_order", + columnList = "store_id, deleted, sort_order")) +public class Menu extends BaseTimeEntity { +``` + +- 같다(`=`)로 거르는 컬럼 둘을 앞에, 정렬 컬럼을 뒤에 뒀다. `deleted` 는 값 종류가 적지만 앞에 같다 조건으로 있어야 `sort_order` 순서로 바로 읽힌다 +- 두 앱 모두 `ddl-auto: update` 라 배포 때 Hibernate 가 만든다. EC2·Mac 모두 앱 시작 후 생성된 것을 확인했다(`idx_menus_store_id_deleted_sort_order(store_id, deleted, sort_order)`) +- 측정에서는 `run.sh` 가 단계에 맞게 인덱스를 지우거나 다시 만든다(앱이 시작할 때 만들기 때문) + +**실행 계획** (Mac, 같은 시드 데이터) + +``` +EXPLAIN SELECT m.*, i.* FROM menus m LEFT JOIN menu_images i ON i.menu_id = m.id + WHERE m.store_id = 7 AND m.deleted = 0 ORDER BY m.sort_order, i.id + +인덱스 없음 m: type ALL, rows 3978, filtered 5.00, Using where; Using temporary; Using filesort + i: type ref, key FK(menu_id), rows 1 +인덱스 있음 m: type ref, key idx_menus_store_id_deleted_sort_order, ref const,const, rows 20, Using temporary; Using filesort + i: type ref, key FK(menu_id), rows 1 +``` + +`Using filesort` 는 두 번째 테이블 컬럼(`i.id`)으로도 정렬해서 남는다. 20행 정렬이다. + +## 3. 실행 명령 + +```bash +RATES="200 1000 2000 3000 4000" performance/menu-cache/run.sh bench join-idx # 캐시 끔(ttl 0s), 인덱스 있음 +``` + +## 4. 관찰 결과 + +**읽는 행이 4,042 → 62 로 줄었다 (EC2, 200 req/s)** + +| | 1단계 JOIN | 2단계 + 인덱스 | +|---|---:|---:| +| 요청당 읽은 행 | 4,042 | 62 | +| 요청당 쿼리 시간: `menus` (JOIN) | 2.09ms | 0.32ms | +| 요청당 쿼리 시간: `stores` | 0.14ms | 0.15ms | +| MySQL CPU | 0.46코어 | 0.11코어 | +| p95 | 4.2ms | 2.4ms | + +**속도별 (EC2)** + +| 목표 req/s | 처리 req/s | p50 | p95 | p99 | 못 보낸 요청 | 요청당 읽은 행 | MySQL CPU | user-api CPU | +|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 200 | 200 | 1.8ms | 2.4ms | 2.8ms | 0 | 62 | 0.11 | 0.41 | +| 1000 | 998 | 1.6ms | 1.8ms | 2.6ms | 0 | 62 | 0.50 | 0.92 | +| 2000 | 1,995 | 13.4ms | 57.6ms | 74.5ms | 0 | 56 | 0.99 | 2.00 | +| 3000 | 2,356 | 799ms | 937ms | 1,067ms | 36,353 | 44 | 0.99 | 2.21 | +| 4000 | 2,333 | 822ms | 976ms | 1,100ms | 97,755 | 44 | 0.99 | 2.25 | + +- p95 200ms 안에서 처리한 최대 부하: 500 → 2,000 (약 4배). 2,000 에서 MySQL 이 1코어를 다 썼고, 3,000 을 넣으면 2,356 에서 멈췄다 +- 요청당 MySQL CPU: 2.3ms (200 req/s) → 0.50ms (1,000 req/s) +- 3,000·4,000 에서 요청당 읽은 행이 44 로 줄어든 것은 동시에 온 같은 주점 요청이 로딩을 나눠 받아서다(히트율 29.7%·29.6%) + +Mac 에서도 한계가 비슷하게 올랐다. 1,000 req/s 를 p95 4.6ms 로 처리했고, 3,000 을 넣으면 2,205 에서 멈췄다. + +## 5. 보장 범위와 해결하지 못한 문제 + +- 인덱스 이름과 컬럼이 엔티티(`Menu`)와 `run.sh` 두 곳에 적혀 있다. 한쪽만 바꾸면 측정이 어긋난다 +- 운영 테이블에 인덱스를 만드는 동안의 잠금은 확인하지 않았다. MySQL 8 의 보조 인덱스 생성은 온라인 DDL 이고, `menus` 는 작은 테이블이다 +- `Using temporary; Using filesort` 가 남는다(2절) + +## 6. 다음에 볼 것 + +- 남은 요청당 쿼리 시간은 `menus` 0.32ms + `stores` 0.15ms 다. 거의 바뀌지 않는 같은 데이터를 요청마다 다시 읽는다 → 3단계 캐시 diff --git a/performance/menu-cache/03-cache.md b/performance/menu-cache/03-cache.md new file mode 100644 index 00000000..4927e3bb --- /dev/null +++ b/performance/menu-cache/03-cache.md @@ -0,0 +1,148 @@ +# 3단계: 로컬 캐시 + Redis Pub/Sub 무효화 + +실행 원본: [EC2](results/2026-10-26-ec2.md), [Mac](results/2026-10-26-mac.md) + +## 1. 질문 + +2단계 뒤에도 손님 메뉴 조회는 요청마다 DB 에 갔다(주점 SELECT + 메뉴·이미지 JOIN). 2,000 req/s 에서 MySQL 이 1코어를 다 썼다. 메뉴는 거의 바뀌지 않는데 같은 데이터를 매번 다시 읽는다. + +캐시를 붙이는 데 걸리는 제약 +- **쓰는 서버와 읽는 서버가 다르다.** 메뉴·품절은 admin-api 가 바꾸고 user-api 가 읽는다. user-api 에 로컬 캐시만 두면 admin-api 의 변경을 모른다 +- **품절은 바로 보여야 한다.** 짧은 TTL 은 히트율을 떨어뜨리고, 긴 TTL 은 품절된 메뉴를 그동안 보여 준다 +- **Redis 는 웨이팅 대기열과 함께 쓴다.** 조회마다 Redis 를 거치는 전역 캐시는 대기열과 자원을 나눠 쓴다 + +채널톡 기술 블로그의 Distributed Cache 도입기([1](https://tech.channel.io/kr/articles/tech-distributed-cache-1-67a392c5), [2](https://docs.channel.io/tech-blog/ko/articles/tech-distributed-cache-2-f77c0868), [3](https://docs.channel.io/tech-blog/ko/articles/tech-cache-rdb-4cc0bbf4))는 같은 문제를 로컬 캐시 + Redis Pub/Sub 키 무효화로 풀었다. 짧은 TTL 의 로컬 캐시와 Redis 전역 캐시(단일 노드 CPU 70%, 롤백)를 거친 뒤다. 그 구조를 그대로 가져왔다. + +질문 +- 캐시를 켜면 DB 쿼리와 MySQL CPU 가 얼마나 줄고, 한계가 얼마나 오르는가? +- 부하 중에 품절을 바꾸면 손님 응답에 바로 반영되는가? +- 무효화 직후 같은 주점 요청이 몰리면 DB 로 새는가? + +## 2. 바꾼 것 + +``` +[조회] 손님 → user-api → 로컬 캐시(Caffeine) 히트 → 응답 + └ 미스 → MySQL 에서 읽어 캐시에 넣음 (같은 키 동시 미스는 한 번만 읽음) + +[변경] 점주 → admin-api → MySQL 커밋 + └ 커밋 후 → Redis PUBLISH cache:menus "INVALIDATE-{storeId}" + └ 구독 중인 모든 user-api 가 그 주점 키만 지움 +``` + +| 파일 | 하는 일 | +|---|---| +| `domain-redis` `DistributedCachedDao` | Caffeine 캐시 + 채널 구독. `get(key)` = `cache.get(key, this::load)`, 메시지를 받으면 키를 지운다 | +| `domain-redis` `CacheInvalidationPublisher` | `INVALIDATE-{key}` / `CLEAR` 를 커밋 후 발행 | +| user-api `DistributedCachedMenuDao` | 키 = storeId, 값 = 주점 메뉴 목록. TTL 1일(`nowait.cache.menu.ttl`, 0s 면 끔), 최대 1,000개 | +| admin-api `MenuService`·`MenuImageService` | 무효화 7곳: 메뉴 생성·수정·순서 변경·삭제·품절 토글, 이미지 업로드·삭제 | + +- 채널톡 구현처럼 Redis 에는 데이터가 아니라 키만 보낸다. 캐시에 값을 넣는(put) 경로는 없고, 지운 뒤 다음 조회가 DB 에서 다시 읽는다 +- Redis 에는 메뉴가 바뀔 때만 메시지가 오간다. 조회 경로는 Redis 를 거치지 않는다 + +```java +public V get(String key) { + return cache.get(key, this::load); +} + +@Override +public void onMessage(Message message, byte[] pattern) { + String body = new String(message.getBody(), StandardCharsets.UTF_8); + if (CacheInvalidationPublisher.CLEAR.equals(body)) { + cache.invalidateAll(); + } else if (body.startsWith(CacheInvalidationPublisher.INVALIDATE_PREFIX)) { + cache.invalidate(body.substring(CacheInvalidationPublisher.INVALIDATE_PREFIX.length())); + } +} +``` + +**정합성을 지키는 장치 세 가지** + +1. **커밋 후에만 발행한다.** 커밋 전에 지우면, 그사이 들어온 조회가 커밋 전 값을 다시 읽어 TTL 동안 캐시한다. `TransactionSynchronization.afterCommit` 에서 발행한다 +2. **재구독하면 전부 비운다.** Pub/Sub 은 연결이 끊긴 동안의 메시지를 다시 주지 않는다. `onChannelSubscribed`(처음 구독할 때와 끊겼다 다시 구독할 때 모두 불린다)에서 `invalidateAll()` +3. **조회 트랜잭션을 없앴다.** user-api 조회에 `@Transactional(readOnly = true)` 가 있으면 주점 조회 때 잡힌 스냅샷(REPEATABLE READ) 안에서 캐시 미스 로딩이 일어난다. 무효화 직후에도 커밋 전 메뉴를 읽어 하루 동안 캐시할 수 있다. 쿼리마다 따로 커밋된 최신 값을 읽게 했다 + +AS-IS(2단계)와 TO-BE(3단계)는 같은 jar 에서 TTL 설정만 다르다(`0s` / `1d`). 두 단계의 차이는 캐시 하나다. + +## 3. 실행 명령 + +```bash +RATES="200 1000 2000 3000 4000" performance/menu-cache/run.sh bench join-idx # AS-IS: 캐시 끔 +RATES="200 1000 2000 4000 6000" performance/menu-cache/run.sh bench cache # TO-BE +performance/menu-cache/run.sh delay # 400 req/s 부하 중 품절 토글 30회 → 손님 응답 반영 확인 (probe.sh) +``` + +## 4. 관찰 결과 + +**DB 부하 (EC2, 1,000 req/s 60초)** + +| | AS-IS | TO-BE | 감소 | +|---|---:|---:|---:| +| 메뉴 쿼리 (`menus`, 메뉴·이미지 JOIN) | 59,808 | 123 | 99.8% | +| 주점 쿼리 (`stores`) | 59,982 | 59,960 | 0% (캐시 대상 아님) | +| RDB 쿼리 합계 | 119,790 | 60,083 | 49.8% (요청당 2 → 1) | +| 요청당 읽은 행 | 62 | 1 | 98% | +| 요청당 쿼리 시간 | 0.45ms | 0.13ms | 71% | +| MySQL CPU | 0.50코어 | 0.15코어 | 70% | +| 히트율 | - | 99.9% | | +| p95 | 1.8ms | 1.1ms | | + +- TO-BE 에 남은 메뉴 쿼리 123건은 초당 1건 품절 토글 때문이다. 점주 쪽 토글 처리가 메뉴를 읽은 약 60건과, 무효화된 키를 다시 채운 약 60건이다 +- RDB 쿼리 합계는 절반만 줄었다. 요청마다 나가는 주점 조회를 캐시하지 않았기 때문이다 + +**속도별 (EC2)** + +| 목표 req/s | AS-IS 처리 | AS-IS p95 | AS-IS MySQL CPU | TO-BE 처리 | TO-BE p95 | TO-BE MySQL CPU | TO-BE user-api CPU | +|---:|---:|---:|---:|---:|---:|---:|---:| +| 200 | 200 | 2.4ms | 0.11 | 200 | 1.6ms | 0.04 | 0.36 | +| 1000 | 998 | 1.8ms | 0.50 | 998 | 1.1ms | 0.15 | 0.69 | +| 2000 | 1,995 | 57.6ms | 0.99 | 1,996 | 1.3ms | 0.31 | 1.35 | +| 3000 | 2,356 | 937ms | 0.99 | - | - | - | - | +| 4000 | 2,333 | 976ms | 0.99 | 3,992 | 2.2ms | 0.59 | 2.62 | +| 6000 | - | - | - | 5,894 | 256ms | 0.88 | 3.89 | + +- p95 200ms 안에서 처리한 최대 부하: 2,000 → 4,000 (2배). 최대 처리량: 2,356 → 5,894 +- 같은 2,000 req/s 에서 p95 57.6ms → 1.3ms +- 요청당 MySQL CPU: 0.50ms → 0.15ms (1,000 req/s) +- AS-IS 는 과부하 구간에서 같은 주점 동시 요청이 로딩을 나눠 받아(2,000 에서 히트율 10.2%, 3,000 이상 약 30%) 실제보다 조금 유리하게 측정됐다. 캐시 효과는 표보다 조금 크다 + +**무효화 직후 몰려도 DB 로 새지 않았다** (EC2, 초당 1건 품절 토글, 60초 동안의 메뉴 쿼리) + +| 목표 req/s | 200 | 1,000 | 2,000 | 4,000 | 6,000 | +|---|---:|---:|---:|---:|---:| +| 메뉴 쿼리 | 123 | 123 | 122 | 122 | 123 | + +- 부하가 30배가 돼도 메뉴 쿼리 수가 그대로다. 토글은 인기 주점에 몰리게 보냈으니 무효화 직후 같은 주점 요청이 실제로 몰렸다. 그래도 무효화 1번에 다시 채우는 쿼리는 1번이다 +- `cache.get(key, loader)` 는 같은 키를 로딩하는 동안 다른 요청을 기다리게 하고 그 결과를 나눠 준다. 별도 락 없이 서버 한 대 안에서 미스가 하나로 모인다 + +**품절 반영** (EC2, 400 req/s 조회 부하 중 품절 토글 30회. 목록 80% · 단건 20%) + +| 토글 응답 p50 / p95 | 토글 응답 후 바뀐 값 확인 p50 / p95 / 최대 | 옛 값을 받은 조회 | +|---|---|---:| +| 17ms / 18ms | 9ms / 10ms / 11ms | 0 / 30회 | + +- 30번 모두 토글 응답 직후 첫 조회부터 바뀐 값이 나왔다. "바뀐 값 확인" 9ms 는 조회 한 번에 걸린 시간이다 +- 무효화는 커밋 후, HTTP 응답 전에 발행된다. 같은 서브넷의 Pub/Sub 전달이 응답보다 먼저 끝났다 + +**재구독** (Mac 로컬 E2E, 2026-10-26, 원본 기록 없음. 콘솔 출력으로만 확인) +- Redis 에서 `CLIENT KILL TYPE pubsub` 으로 구독 연결을 끊자 약 200ms 뒤 다시 구독했고(`PUBSUB NUMSUB cache:menus` = 1), 바로 다음 조회는 캐시 미스였다(전부 비워짐) + +**다음 한계**: 6,000 req/s 에서 user-api CPU 가 3.89/4 코어였다. MySQL 은 0.88코어로, 남은 쿼리는 주점 조회뿐이다. k6 인스턴스 CPU 는 재지 않아 못 보낸 요청 5,180건이 부하 발생기 쪽 한계인지는 가르지 못했다. + +## 5. 보장 범위와 해결하지 못한 문제 + +- 결과적 일관성이다. 커밋과 무효화 메시지 처리 사이(같은 서브넷에서 수 ms)에는 옛 값이 나갈 수 있다. 측정에서는 한 번도 보이지 않았다(4절) +- **무효화 메시지는 유실될 수 있다.** + - 연결이 끊긴 동안 놓친 메시지는 재구독 때 전부 비워 메운다 + - 커밋 때 Redis 가 죽어 발행 자체가 실패하면 옛 값이 TTL(1일)까지 남는다. 에러 로그를 남기고 `redis-cli PUBLISH cache:menus CLEAR` 로 수동 복구한다. 재시도·아웃박스는 넣지 않았다. TTL 을 없애지 않은 이유이기도 하다(채널톡도 같은 이유로 남겼다) +- **미스가 하나로 모이는 것은 서버 한 대 안에서다.** user-api 가 N대면 무효화 한 번에 DB 조회도 N번이다. 분산 락은 넣지 않았다(미스마다 Redis 왕복이 늘고, Redis 를 대기열과 함께 쓰는 문제가 돌아온다) +- **로딩하는 동안 같은 주점 요청은 기다린다.** 옛 값을 주면서 다시 읽는 방식(refresh)은 품절 직후 품절 전 메뉴를 보여 주므로 택하지 않았다. DB 가 느려지면 그 주점 요청도 함께 느려진다 +- `CLEAR` 나 배포 직후에는 모든 키가 비어 있다. 주점 200개면 서버마다 최대 200번 다시 읽는다 +- 주점 조회는 캐시하지 않았다. 캐시를 켠 뒤 MySQL 부하의 거의 전부다 +- user-api 한 대로만 쟀다. 여러 대에 무효화가 퍼지는 것과, 그때의 반영 시간은 재지 않았다 + +## 6. 다음에 볼 것 + +- 주점 조회 캐시: 같은 구조(키 = publicCode)로 요청당 남은 쿼리 1개를 없애기. 주점 정보 변경 지점에 무효화를 넣어야 한다 +- user-api 를 두 대 이상 띄워 무효화가 모든 서버에 퍼지는지, 반영 시간이 어떻게 되는지 +- 발행 실패 대비: 실패한 무효화를 재시도하거나, 커밋과 함께 남겨 두고(아웃박스) 나중에 다시 보내기 +- k6 인스턴스 CPU 를 함께 기록해, 6,000 req/s 의 한계가 앱 서버인지 부하 발생기인지 가르기 diff --git a/performance/menu-cache/README.md b/performance/menu-cache/README.md new file mode 100644 index 00000000..62227a36 --- /dev/null +++ b/performance/menu-cache/README.md @@ -0,0 +1,118 @@ +# 메뉴 목록 조회 성능: N+1 → JOIN → 인덱스 → 로컬 캐시 + Redis Pub/Sub 무효화 + +한 단계씩 바꾸고, 같은 조건에서 직접 잰 것만 적는다. 주제는 3단계(캐시)이고, 1·2단계는 3단계의 AS-IS 를 만든 과정이다. + +| 단계 | 주제 | 상태 | +|---|---|---| +| 1 | [N+1 을 JOIN 한 번으로](01-join.md) | 구현·측정 완료 | +| 2 | [menus 에 (store_id, deleted, sort_order) 인덱스](02-index.md) | 구현·측정 완료 | +| 3 | [로컬 캐시 + Redis Pub/Sub 무효화](03-cache.md) | 구현·측정 완료 | + +실행 원본: [EC2 측정 (2026-10-26)](results/2026-10-26-ec2.md), [Mac 측정 (2026-10-26)](results/2026-10-26-mac.md) + +## 한눈에 (EC2, 2026-10-26) + +| 단계 | 요청당 쿼리 | 요청당 읽은 행 | 요청당 MySQL CPU (잰 속도) | p95 200ms 안에서 처리한 최대 부하 | 그때 p95 | +|---|---:|---:|---|---:|---:| +| 기준: N+1 | 22 | 4,041 | 4.7ms (100 req/s) | 100 | 10.0ms | +| 1: JOIN | 2 | 4,042 | 2.3ms (200 req/s) | 500 | 129.5ms | +| 2: + 인덱스 | 2 | 62 | 0.50ms (1,000 req/s) | 2,000 | 57.6ms | +| 3: + 캐시 | 1 | 1 | 0.15ms (1,000 req/s) | 4,000 | 2.2ms | + +- 요청당 MySQL CPU 는 MySQL 이 포화되지 않은 속도에서 잰 값이다. 낮은 속도일수록 MySQL 의 기본 사용량이 섞여 조금 크게 나온다 +- 3단계에서 남은 쿼리 1개는 주점 조회(`stores`)다. 메뉴 쿼리는 1,000 req/s 60초 동안 59,808 → 123 (99.8% 감소) +- 3단계는 6,000 req/s 에서 user-api CPU 가 3.9/4 코어로 먼저 찼다. 한계가 DB 에서 앱 서버로 옮겨갔다 + +## 단계별 설정 + +같은 user-api jar 에서 설정만 바꾼 단계는 차이가 그 설정 하나다. + +| 단계 | `run.sh bench` 이름 | user-api jar | menus 인덱스 | 캐시 (`nowait.cache.menu.ttl`) | +|---|---|---|---|---| +| 기준 | `nplus1` | `user-asis.jar` (커밋 `d20c593`, 캐시 작업 전) | 없음 | 캐시 코드 없음 | +| 1 | `join` | `user-tobe.jar` | 없음 | `0s` (끔) | +| 2 | `join-idx` | `user-tobe.jar` | 있음 | `0s` (끔) | +| 3 | `cache` | `user-tobe.jar` | 있음 | `1d` (기본값) | + +- admin-api 는 모든 단계에서 `admin.jar`(작업 트리)를 쓴다. 손님 조회 경로에는 들어가지 않고, 품절 토글과 무효화 발행만 한다 +- 기준 단계의 user-api 는 조회에 `@Transactional(readOnly = true)` 가 붙어 있다. 1단계부터는 없다(3단계 2절). 그래서 기준 → 1단계 차이에는 트랜잭션 설정 문장도 섞여 있다(1단계 4절) + +## 공통 조건 + +**인스턴스** (쿠폰 발급 토이 프로젝트의 실험용 EC2 세 대를 빌려 썼다) + +| 이름 | 유형 | 하는 일 | +|---|---|---| +| db | c7g.large (2 vCPU, 4GiB) | MySQL 8.0.36·Redis 7.2 컨테이너 (각 1 CPU / 1GB 제한). 스키마는 따로 `nowait_lab` | +| app | c7g.xlarge (4 vCPU, 8GiB) | user-api(8081)·admin-api(8085). 둘 다 `-Xms256m -Xmx512m -XX:+UseG1GC` | +| k6 | c7g.large (2 vCPU, 4GiB) | 부하 발생 (k6 v2.1.0) | + +서울 리전, 세 대 같은 서브넷, Ubuntu 24.04 (Arm). 앱은 `user-local`·`admin-local` 프로필에 DB·Redis 주소만 바꾸고, SQL 로그를 끄고, Discord 알림 웹훅을 막아 띄운다. + +**데이터** (`seed.sql`): 주점 200 × 메뉴 20 = 메뉴 4,000건, 이미지 4,000장. 메뉴마다 이미지 1장인데, 2번 메뉴는 2장, 20번 메뉴는 0장이다(JOIN 결과를 메뉴별로 묶는지, 이미지 없는 메뉴가 빠지지 않는지 보려고). + +**트래픽** (`menu-load.js`) +- 손님 메뉴 목록 조회 `GET /v1/stores/{publicCode}/menus` 를 일정한 속도로(constant-arrival-rate) +- 주점은 앞 번호일수록 자주 고른다(`floor(200 × u²) + 1`). 인기 주점에 몰리는 모양 +- 동시에 점주 품절 토글 `PATCH /admin/menus/toggle-soldout/{id}` 초당 1건 (각 주점의 1번 메뉴는 반영 지연 측정용으로 빼 둔다) + +**순서**: 단계마다 user-api 를 새로 띄우고 품절을 초기화하고 인덱스를 단계에 맞춘다 → 초당 100건 20초 예열(버림) → 속도마다 60초씩 1회. + +**측정값** + +| 값 | 어디서 | +|---|---| +| 처리 req/s, p50·p95·p99, 실패, 못 보낸 요청 | k6 요약(조회 시나리오만). 처리 req/s 는 끝난 요청 수 ÷ 실행 시간(마무리 대기 포함) | +| 테이블별 쿼리 수·읽은 행·쿼리 시간 | `performance_schema.events_statements_summary_by_digest` (`digest.sql`). 속도마다 비운다. admin 쿼리(토글·로그인, 초당 1건 수준)가 섞인다. 메뉴·이미지 JOIN 쿼리는 `menus` 로 센다 | +| MySQL CPU | MySQL 컨테이너 cgroup `usage_usec` 증가량 ÷ 측정 시간 | +| user-api CPU | user-api JVM 누적 CPU 시간(`ps -o time`) 증가량 ÷ 측정 시간 | +| 히트율 | Micrometer `cache_gets_total{cache="cache:menus"}` 증가량 | + +**p95 200ms 안에서 처리한 최대 부하**: 처리 req/s ≥ 목표의 99%, 못 보낸 요청 0, p95 ≤ 200ms 를 모두 만족한 가장 높은 목표 속도. 잰 속도 간격이 넓어(예: 2,000 다음 3,000) 실제 한계는 이보다 높을 수 있다. + +## 정합성 확인 + +`run.sh verify` 가 기존 코드(user `user-asis.jar`, admin `admin-asis.jar`)와 바꾼 코드를 동시에 띄우고, 200개 주점 전부의 메뉴 목록 응답을 문자열로 비교한다. + +| 비교 | EC2 | Mac | +|---|---|---| +| user 메뉴 목록 | 같음 200 / 다름 0 | 같음 200 / 다름 0 | +| admin 메뉴 목록 | 같음 200 / 다름 0 | 같음 200 / 다름 0 | + +단위 테스트: `MenuRepositoryTest`(H2. JOIN 쿼리 1번, 정렬, 이미지 묶기, 삭제 메뉴·다른 주점 제외, 이미지 없는 메뉴), `DistributedCachedDaoTest`(키 무효화, CLEAR, 재구독 시 비우기). + +## 실행 방법 + +```bash +# jar 네 벌. 기존 코드는 캐시 작업 전 커밋에서 빌드한다 +# application-*.yml 은 git 에 없으므로 작업 트리에서 복사하고, +# admin 은 Mac 에서 빌드하면 storePayment 패키지 대소문자 충돌로 뜨지 않아 storepayment 패키지 수정 3파일도 복사한다 +git worktree add --detach /tmp/asis d20c593 +cp -R nowait-app-user-api/src/main/resources/. /tmp/asis/nowait-app-user-api/src/main/resources/ +cp -R nowait-app-admin-api/src/main/resources/. /tmp/asis/nowait-app-admin-api/src/main/resources/ +(cd /tmp/asis && ./gradlew :nowait-app-user-api:bootJar :nowait-app-admin-api:bootJar) # → user-asis.jar, admin-asis.jar +./gradlew :nowait-app-user-api:bootJar :nowait-app-admin-api:bootJar # → user-tobe.jar, admin.jar + +# EC2 (쿠폰 프로젝트의 build/ec2/hosts.env 를 읽는다) +JARS= performance/menu-cache/run.sh setup +performance/menu-cache/run.sh verify +RATES="100 200 400" performance/menu-cache/run.sh bench nplus1 +RATES="200 500 1000" performance/menu-cache/run.sh bench join +RATES="200 1000 2000 3000 4000" performance/menu-cache/run.sh bench join-idx +RATES="200 1000 2000 4000 6000" performance/menu-cache/run.sh bench cache +performance/menu-cache/run.sh delay +performance/menu-cache/run.sh cleanup # 앱 종료, nowait_lab 삭제 + +# Mac 한 대 (MySQL·Redis 는 도커, 각 1 CPU / 1GB) +LOCAL=1 JARS= performance/menu-cache/run.sh setup +LOCAL=1 RATES="200 500 1000" performance/menu-cache/run.sh bench nplus1 join join-idx cache +``` + +결과는 `performance/menu-cache/out/<단계>/` 에 쌓이고(git 에 올리지 않는다) `report.md` 로 요약된다. + +## 공통 한계 + +- 운영 트래픽이 아니라 실험 환경에서 잰 값이다. 데이터 규모(메뉴 4,000건, 주점당 20개)가 바뀌면 단계별 효과 크기도 바뀐다 +- 속도마다 1회만 쟀다(쿠폰 측정은 2회) +- k6 인스턴스 CPU 는 기록하지 않았다. 가장 높은 속도(6,000 req/s)의 "못 보낸 요청" 이 부하 발생기 한계인지 가르지 못한다 +- user-api 는 한 대로 쟀다. 여러 대에 무효화가 퍼지는 것은 재지 않았다 diff --git a/performance/menu-cache/digest.sql b/performance/menu-cache/digest.sql new file mode 100644 index 00000000..c603b4bc --- /dev/null +++ b/performance/menu-cache/digest.sql @@ -0,0 +1,14 @@ +-- 측정 구간 동안 테이블별 쿼리 수, 읽은 행 수, 누적 실행 시간(ms). admin(로그인·토글) 쿼리도 같은 스키마라 섞이지만 초당 1건 수준 +-- 메뉴·이미지 JOIN 쿼리는 FROM `menus` 로 시작하므로 menus 로 센다 +SELECT CASE + WHEN DIGEST_TEXT LIKE '%FROM `menu_images`%' THEN 'menu_images' + WHEN DIGEST_TEXT LIKE '%FROM `menus`%' THEN 'menus' + WHEN DIGEST_TEXT LIKE '%FROM `stores`%' THEN 'stores' + ELSE 'other' END AS tbl, + SUM(COUNT_STAR) AS calls, + SUM(SUM_ROWS_EXAMINED) AS rows_examined, + ROUND(SUM(SUM_TIMER_WAIT) / 1e9, 1) AS total_ms +FROM performance_schema.events_statements_summary_by_digest +WHERE SCHEMA_NAME = 'nowait_lab' +GROUP BY tbl +ORDER BY tbl; diff --git a/performance/menu-cache/menu-load.js b/performance/menu-cache/menu-load.js new file mode 100644 index 00000000..aabf31f9 --- /dev/null +++ b/performance/menu-cache/menu-load.js @@ -0,0 +1,59 @@ +// 손님 메뉴 조회(목록 LIST_RATIO, 나머지 단건) + 점주 품절 토글. 인기 주점에 몰리게 앞쪽 주점일수록 자주 고른다(u^2 분포) +import http from 'k6/http'; +import { check } from 'k6'; + +const USER = __ENV.USER_URL; +const ADMIN = __ENV.ADMIN_URL; +const STORES = Number(__ENV.STORES || 50); +const LIST_RATIO = Number(__ENV.LIST_RATIO || 1); +const MENUS_PER_STORE = 20; +const DURATION = __ENV.DURATION || '60s'; + +export const options = { + discardResponseBodies: true, + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)'], + scenarios: { + reads: { + executor: 'constant-arrival-rate', exec: 'read', rate: Number(__ENV.RATE), timeUnit: '1s', + duration: DURATION, preAllocatedVUs: 200, maxVUs: 2000, + }, + writes: { + executor: 'constant-arrival-rate', exec: 'write', rate: Number(__ENV.WRITE_RATE || 1), timeUnit: '1s', + duration: DURATION, preAllocatedVUs: 2, maxVUs: 10, + }, + }, + // 손님 조회만 따로 집계되게 하는 형식상 기준 (판정용 아님) + thresholds: { + 'http_req_duration{scenario:reads}': ['max>=0'], + 'http_reqs{scenario:reads}': ['count>=0'], + 'http_req_failed{scenario:reads}': ['rate<=1'], + }, +}; + +export function setup() { + const res = http.post(`${ADMIN}/admin/users/login`, + JSON.stringify({ email: 'load@test.com', password: 'loadPass1!' }), + { headers: { 'Content-Type': 'application/json' }, responseType: 'text' }); + return { token: res.json('response.body.accessToken') }; +} + +const pickStore = () => Math.floor(STORES * Math.random() ** 2) + 1; +const code = (store) => 'LOAD' + String(store).padStart(8, '0'); +const menuOf = (store, n) => (store - 1) * MENUS_PER_STORE + n; + +export function read() { + const store = pickStore(); + const res = Math.random() < LIST_RATIO + ? http.get(`${USER}/v1/stores/${code(store)}/menus`, { tags: { name: 'list' } }) + : http.get(`${USER}/v1/stores/${code(store)}/menus/${menuOf(store, Math.floor(Math.random() * MENUS_PER_STORE) + 1)}`, + { tags: { name: 'detail' } }); + check(res, { 'read 200': (r) => r.status === 200 }); +} + +// 각 주점의 1번 메뉴는 반영 지연 측정(probe.sh)용으로 남겨 둔다 +export function write(data) { + const menuId = menuOf(pickStore(), Math.floor(Math.random() * (MENUS_PER_STORE - 1)) + 2); + const res = http.patch(`${ADMIN}/admin/menus/toggle-soldout/${menuId}`, null, + { headers: { Authorization: `Bearer ${data.token}` }, tags: { name: 'toggle' } }); + check(res, { 'toggle 200': (r) => r.status === 200 }); +} diff --git a/performance/menu-cache/probe.sh b/performance/menu-cache/probe.sh new file mode 100755 index 00000000..79da6359 --- /dev/null +++ b/performance/menu-cache/probe.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# 품절 토글 → 손님 조회 반영까지. app 인스턴스에서 실행. 한 줄에: 토글 응답 ms, 토글 응답 후 반영 확인까지 ms, 그 사이 옛 값을 받은 조회 수 +U=localhost:8081/v1/stores/LOAD00000001/menus/1 +T=$(curl -s -X POST localhost:8085/admin/users/login -H 'Content-Type: application/json' \ + -d '{"email":"load@test.com","password":"loadPass1!"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["response"]["body"]["accessToken"])') +sold() { curl -s $U | grep -o '"isSoldOut":[a-z]*'; } +for i in $(seq ${N:-30}); do + b=$(sold) + t0=$(date +%s%3N) + curl -s -o /dev/null -X PATCH localhost:8085/admin/menus/toggle-soldout/1 -H "Authorization: Bearer $T" + t1=$(date +%s%3N) + n=0 + while [ "$(sold)" = "$b" ] && [ $n -lt 1000 ]; do n=$((n + 1)); done + t2=$(date +%s%3N) + echo "$((t1 - t0)) $((t2 - t1)) $n" + sleep 1 +done diff --git a/performance/menu-cache/results/2026-10-26-ec2.md b/performance/menu-cache/results/2026-10-26-ec2.md new file mode 100644 index 00000000..d7b21f78 --- /dev/null +++ b/performance/menu-cache/results/2026-10-26-ec2.md @@ -0,0 +1,149 @@ +# EC2 측정 원본 (2026-10-26 22:26 ~ 22:48) + +실행 조건 +- 인스턴스: db c7g.large, app c7g.xlarge, k6 c7g.large (서울, 같은 서브넷, Ubuntu 24.04 Arm). 쿠폰 프로젝트 실험용 인스턴스를 중지했다 다시 켠 뒤 썼다 +- MySQL 8.0.36 (1 CPU / 1GB), Redis 7.2 (1 CPU / 1GB). 쿠폰 프로젝트 컨테이너를 그대로 쓰고 스키마만 `nowait_lab` 으로 나눴다(측정 후 삭제) +- user-api·admin-api: `-Xms256m -Xmx512m -XX:+UseG1GC`, `user-local`·`admin-local` 프로필, SQL 로그 끔, Discord 웹훅 막음 +- 데이터: 주점 200, 메뉴 4,000, 이미지 4,000, 관리자 1 (`seed.sql`, 주점 수 `STORES=200`) +- 트래픽: 손님 메뉴 목록 조회만(`LIST_RATIO=1`) + 품절 토글 초당 1건(`WRITE_RATE=1`). 단계마다 초당 100건 20초 예열을 버리고, 속도마다 60초 1회 +- 표의 열: 처리 req/s·지연·실패·못 보낸 요청은 k6 조회 시나리오만. stores·menus·menu_images 는 그 테이블 쿼리 호출 수(`performance_schema` digest, admin 쿼리 포함). MySQL CPU 는 컨테이너 cgroup, user-api CPU 는 JVM 누적 CPU 시간에서 계산한 코어 수 + +## 정합성 (verify) + +`performance/menu-cache/run.sh verify` (기존 user :8081·admin :8086, 바꾼 user :8082·admin :8085) + +``` +user 메뉴 목록: 같음 200 / 다름 0 +admin 메뉴 목록: 같음 200 / 다름 0 +admin :8086 메뉴 목록 200 회 — menu_images 4000건 menus 200건 other 2208건 +admin :8085 메뉴 목록 200 회 — menus 200건 other 2208건 +``` + +`other` 2,208건(1회당 약 11개)은 인증 필터의 회원 조회와 트랜잭션 설정 문장이다. 두 버전이 같다. + +## 기준: N+1 (`user-asis.jar`, 인덱스 없음) + +`RATES="100 200 400" performance/menu-cache/run.sh bench nplus1` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 100 | 100 | 9.3 | 10.0 | 11.4 | 0.00% | 0 | - | 6001 | 6062 | 120020 | 22.01 | 4041 | 0.47 | 0.48 | +| 200 | 187 | 1492.1 | 2501.9 | 3157.2 | 0.00% | 285 | - | 11716 | 11777 | 234248 | 22.00 | 4041 | 0.99 | 0.84 | +| 400 | 187 | 10625.1 | 10749.1 | 11704.1 | 0.00% | 10778 | - | 13222 | 13283 | 264384 | 22.00 | 4041 | 0.99 | 0.80 | + +## 1단계: JOIN (`user-tobe.jar`, 캐시 ttl 0s, 인덱스 없음) + +`RATES="200 500 1000" performance/menu-cache/run.sh bench join` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 200 | 3.5 | 4.2 | 4.7 | 0.00% | 0 | 0.0% | 12001 | 12062 | 0 | 2.01 | 4042 | 0.46 | 0.43 | +| 500 | 499 | 74.2 | 129.5 | 160.6 | 0.00% | 0 | 16.0% | 29989 | 25248 | 0 | 1.84 | 3395 | 0.99 | 0.54 | +| 1000 | 658 | 2893.1 | 3169.5 | 3322.6 | 0.00% | 18517 | 38.4% | 41466 | 25627 | 0 | 1.62 | 2492 | 0.99 | 0.66 | + +## 2단계: JOIN + 인덱스 (`user-tobe.jar`, 캐시 ttl 0s, 인덱스 있음) + +`RATES="200 1000 2000 3000 4000" performance/menu-cache/run.sh bench join-idx` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 200 | 1.8 | 2.4 | 2.8 | 0.00% | 0 | 0.0% | 11999 | 12062 | 0 | 2.00 | 62 | 0.11 | 0.41 | +| 1000 | 998 | 1.6 | 1.8 | 2.6 | 0.00% | 0 | 0.4% | 59982 | 59808 | 0 | 2.00 | 62 | 0.50 | 0.92 | +| 2000 | 1995 | 13.4 | 57.6 | 74.5 | 0.00% | 0 | 10.2% | 119915 | 107749 | 0 | 1.90 | 56 | 0.99 | 2.00 | +| 3000 | 2356 | 799.0 | 937.0 | 1067.2 | 0.00% | 36353 | 29.7% | 143616 | 101090 | 0 | 1.70 | 44 | 0.99 | 2.21 | +| 4000 | 2333 | 821.6 | 975.9 | 1100.3 | 0.00% | 97755 | 29.6% | 142196 | 100222 | 0 | 1.70 | 44 | 0.99 | 2.25 | + +## 3단계: 캐시 (`user-tobe.jar`, 캐시 ttl 1d, 인덱스 있음) + +`RATES="200 1000 2000 4000 6000" performance/menu-cache/run.sh bench cache` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 200 | 1.1 | 1.6 | 1.9 | 0.00% | 0 | 99.5% | 12000 | 123 | 0 | 1.01 | 1 | 0.04 | 0.36 | +| 1000 | 998 | 1.0 | 1.1 | 1.4 | 0.00% | 0 | 99.9% | 59960 | 123 | 0 | 1.00 | 1 | 0.15 | 0.69 | +| 2000 | 1996 | 1.1 | 1.3 | 1.8 | 0.00% | 0 | 99.9% | 118113 | 122 | 0 | 0.99 | 1 | 0.31 | 1.35 | +| 4000 | 3992 | 1.2 | 2.2 | 4.0 | 0.00% | 0 | 100.0% | 239756 | 122 | 0 | 1.00 | 1 | 0.59 | 2.62 | +| 6000 | 5894 | 69.3 | 255.6 | 283.5 | 0.00% | 5180 | 100.0% | 354741 | 123 | 0 | 1.00 | 1 | 0.88 | 3.89 | + +## 테이블별 쿼리 (호출 수 / 읽은 행 / 누적 실행 시간 ms) + +`digest.sql`, 속도마다 비우고 60초 측정 뒤 읽음. 메뉴·이미지 JOIN 은 `menus` 로 센다. "그 밖" 은 트랜잭션 설정 문장(`SET autocommit`, `SET SESSION TRANSACTION ...`, `COMMIT`)과 admin 쪽 쿼리다. + +### 기준: N+1 + +| 목표 req/s | stores | menus | menu_images | 그 밖 (호출 / ms) | +|---:|---|---|---|---| +| 100 | 6,001 / 6,001 / 727 | 6,062 / 24,124,081 / 11,283 | 120,020 / 120,020 / 12,129 | 30,623 / 1,399 | +| 200 | 11,716 / 11,716 / 2,787 | 11,777 / 47,098,381 / 126,403 | 234,248 / 234,285 / 64,541 | 59,194 / 4,272 | +| 400 | 13,222 / 13,223 / 3,193 | 13,283 / 53,156,521 / 145,156 | 264,384 / 264,419 / 72,100 | 66,733 / 5,977 | + +### 1단계: JOIN + +| 목표 req/s | stores | menus | menu_images | 그 밖 (호출 / ms) | +|---:|---|---|---|---| +| 200 | 12,001 / 12,001 / 1,640 | 12,062 / 48,496,102 / 25,083 | - | 618 / 417 | +| 500 | 29,989 / 29,999 / 8,969 | 25,248 / 101,809,015 / 381,505 | - | 618 / 464 | +| 1,000 | 41,466 / 41,476 / 13,576 | 25,627 / 103,336,512 / 377,917 | - | 608 / 530 | + +### 2단계: JOIN + 인덱스 + +| 목표 req/s | stores | menus | menu_images | 그 밖 (호출 / ms) | +|---:|---|---|---|---| +| 200 | 11,999 / 12,001 / 1,758 | 12,062 / 732,061 / 3,810 | - | 618 / 398 | +| 1,000 | 59,982 / 59,994 / 7,801 | 59,808 / 3,646,458 / 18,708 | - | 618 / 424 | +| 2,000 | 119,915 / 119,976 / 23,663 | 107,749 / 6,570,187 / 69,006 | - | 608 / 481 | +| 3,000 | 143,616 / 143,634 / 31,632 | 101,090 / 6,163,867 / 70,295 | - | 618 / 1,647 | +| 4,000 | 142,196 / 142,235 / 29,993 | 100,222 / 6,110,491 / 67,125 | - | 609 / 459 | + +### 3단계: 캐시 + +| 목표 req/s | stores | menus | menu_images | 그 밖 (호출 / ms) | +|---:|---|---|---|---| +| 200 | 12,000 / 12,001 / 1,748 | 123 / 3,843 / 31 | - | 618 / 400 | +| 1,000 | 59,960 / 59,988 / 7,204 | 123 / 3,843 / 31 | - | 618 / 402 | +| 2,000 | 118,113 / 119,225 / 14,857 | 122 / 3,782 / 31 | - | 618 / 409 | +| 4,000 | 239,756 / 239,934 / 29,361 | 122 / 3,782 / 32 | - | 619 / 422 | +| 6,000 | 354,741 / 354,799 / 44,472 | 123 / 3,843 / 33 | - | 618 / 421 | + +- 기준의 "그 밖" 호출(요청당 약 5개)은 조회 `@Transactional(readOnly = true)` 의 설정 문장이다. 1단계부터는 admin 쪽 것만 남는다(60초에 약 618개) +- 3단계 `menus` 123건 ≈ 품절 토글 60번 × (admin 의 메뉴 조회 1 + 무효화 뒤 다시 채우기 1) + +## 품절 반영 지연 (delay) + +`performance/menu-cache/run.sh delay`: 캐시 단계 user-api 에 400 req/s 조회(목록 80%, 단건 20%)를 120초 걸고, 15초 뒤 app 인스턴스에서 `probe.sh` 를 돌렸다. 1번 주점 1번 메뉴(k6 토글 대상에서 뺀 메뉴)를 토글하고, 단건 조회를 반복해 바뀐 값이 나올 때까지 잰다. + +``` +토글 응답 ms 토글 응답 후 바뀐 값 확인까지 ms 그사이 옛 값을 받은 조회 수 +17 9 0 +18 9 0 +16 10 0 +16 9 0 +17 8 0 +16 9 0 +17 9 0 +16 9 0 +17 8 0 +16 8 0 +17 11 0 +16 9 0 +17 8 0 +17 8 0 +16 8 0 +16 8 0 +17 7 0 +17 9 0 +17 8 0 +17 8 0 +16 9 0 +16 9 0 +16 9 0 +17 8 0 +15 9 0 +16 9 0 +16 9 0 +17 7 0 +19 8 0 +16 8 0 +``` + +토글 30회 | 토글 응답 p50 17ms p95 18ms | 응답 후 반영 확인 p50 9ms p95 10ms 최대 11ms | 옛 값을 받은 조회 0회 diff --git a/performance/menu-cache/results/2026-10-26-mac.md b/performance/menu-cache/results/2026-10-26-mac.md new file mode 100644 index 00000000..9a6aeab9 --- /dev/null +++ b/performance/menu-cache/results/2026-10-26-mac.md @@ -0,0 +1,79 @@ +# Mac 측정 원본 (2026-10-26 21:51 ~ 22:13) + +EC2 가 꺼져 있어 먼저 잰 측정이다. 앱·k6·도커가 한 기계(Apple Silicon 8코어, 16GB)의 CPU 를 나눠 쓰므로 절대값보다 단계 간 차이를 본다. + +실행 조건 +- `LOCAL=1 performance/menu-cache/run.sh ...`. MySQL 8.0.36·Redis 7.2-alpine 은 도커(Docker Desktop, 각 `--cpus=1 -m 1g`), user-api·admin-api(JDK 17)와 k6 는 Mac 에서 +- 데이터·트래픽·예열·측정 시간은 EC2 와 같다(주점 200, 목록 조회만, 품절 토글 초당 1건, 예열 100 req/s 20초, 속도마다 60초 1회) +- 2단계·3단계는 200·500·1000 을 먼저 재고, 한계에 닿지 않아 2000·3000 을 따로 다시 쟀다(사이에 앱을 다시 띄웠다) + +## 정합성 (verify) + +``` +user 메뉴 목록: 같음 200 / 다름 0 +admin 메뉴 목록: 같음 200 / 다름 0 +admin :8086 메뉴 목록 200 회 — menus 200건 menu_images 4000건 other 2208건 +admin :8085 메뉴 목록 200 회 — menus 200건 other 2208건 +``` + +## 기준: N+1 + +`LOCAL=1 RATES="200 500 1000" performance/menu-cache/run.sh bench nplus1` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 116 | 11314.5 | 15897.1 | 17061.7 | 0.00% | 3397 | - | 8603 | 8664 | 171732 | 21.96 | 4041 | 0.94 | 0.64 | +| 500 | 104 | 14341.2 | 29772.4 | 30890.2 | 0.00% | 22284 | - | 7720 | 7781 | 154046 | 21.96 | 4041 | 0.94 | 0.51 | +| 1000 | 146 | 12559.0 | 16610.0 | 17431.5 | 0.00% | 49334 | - | 10666 | 10728 | 212876 | 21.96 | 4041 | 0.98 | 0.52 | + +## 1단계: JOIN + +`LOCAL=1 RATES="200 500 1000" performance/menu-cache/run.sh bench join` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 200 | 4.0 | 7.4 | 66.0 | 0.00% | 0 | 0.1% | 12000 | 12047 | 0 | 2.00 | 4037 | 0.41 | 0.47 | +| 500 | 456 | 1101.0 | 2856.8 | 3370.8 | 0.00% | 1269 | 25.8% | 28690 | 21364 | 0 | 1.74 | 2997 | 0.96 | 0.46 | +| 1000 | 430 | 4518.1 | 5281.3 | 5756.3 | 0.00% | 32329 | 28.1% | 27620 | 19953 | 0 | 1.72 | 2907 | 0.99 | 0.44 | + +## 2단계: JOIN + 인덱스 + +`LOCAL=1 RATES="200 500 1000" ... bench join-idx`, 이어서 `LOCAL=1 RATES="2000 3000" ... bench join-idx` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 200 | 3.3 | 6.6 | 13.8 | 0.00% | 0 | 0.0% | 11998 | 12057 | 0 | 2.00 | 62 | 0.21 | 0.47 | +| 500 | 499 | 2.0 | 3.9 | 9.2 | 0.00% | 0 | 0.1% | 29995 | 30031 | 0 | 2.00 | 62 | 0.29 | 0.50 | +| 1000 | 998 | 1.3 | 4.6 | 42.8 | 0.00% | 0 | 0.4% | 59996 | 59793 | 0 | 2.00 | 62 | 0.39 | 0.62 | +| 2000 | 1982 | 7.8 | 151.8 | 312.6 | 0.00% | 816 | 7.4% | 119097 | 110409 | 0 | 1.93 | 57 | 0.92 | 2.10 | +| 3000 | 2205 | 815.7 | 1176.8 | 1469.5 | 0.00% | 45659 | 20.4% | 134241 | 106967 | 0 | 1.80 | 50 | 0.98 | 1.74 | + +## 3단계: 캐시 + +`LOCAL=1 RATES="200 500 1000" ... bench cache`, 이어서 `LOCAL=1 RATES="2000 3000" ... bench cache` + +| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 200 | 199 | 2.3 | 5.2 | 12.5 | 0.00% | 0 | 99.5% | 11993 | 124 | 0 | 1.01 | 1 | 0.11 | 0.42 | +| 500 | 499 | 1.4 | 4.1 | 13.9 | 0.00% | 0 | 99.8% | 29973 | 122 | 0 | 1.00 | 1 | 0.19 | 0.37 | +| 1000 | 997 | 1.0 | 2.2 | 8.3 | 0.00% | 0 | 99.9% | 59961 | 122 | 0 | 1.00 | 1 | 0.24 | 0.45 | +| 2000 | 1996 | 0.6 | 2.5 | 11.7 | 0.00% | 0 | 99.9% | 119959 | 125 | 0 | 1.00 | 1 | 0.26 | 0.91 | +| 3000 | 2992 | 0.5 | 3.0 | 17.8 | 0.00% | 162 | 100.0% | 179802 | 121 | 0 | 1.00 | 1 | 0.35 | 0.89 | + +## EXPLAIN (메뉴·이미지 JOIN, 주점 7) + +``` +인덱스 있음 +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE m ref idx_menus_store_id_deleted_sort_order idx_menus_store_id_deleted_sort_order 9 const,const 20 100.00 Using temporary; Using filesort +1 SIMPLE i ref FKqpgn1rya57y74a1ncxnc476mj FKqpgn1rya57y74a1ncxnc476mj 8 nowait_lab.m.id 1 100.00 NULL + +인덱스 없음 +1 SIMPLE m ALL NULL NULL NULL NULL 3978 5.00 Using where; Using temporary; Using filesort +1 SIMPLE i ref FKqpgn1rya57y74a1ncxnc476mj FKqpgn1rya57y74a1ncxnc476mj 8 nowait_lab.m.id 1 100.00 NULL +``` + +## EC2 와 다른 점 + +- 순서와 한계의 자리는 EC2 와 같았다(N+1 < JOIN < 인덱스 < 캐시). 숫자는 EC2 가 더 좋았다: 같은 단계·같은 속도에서 p95 가 더 낮고, N+1 의 처리 상한이 116~146 → 187 +- Mac 은 앱·k6 가 MySQL 과 같은 기계에서 돌아, p99 가 들쭉날쭉했다(예: 인덱스 1000 req/s 의 p99 42.8ms) diff --git a/performance/menu-cache/run.sh b/performance/menu-cache/run.sh new file mode 100755 index 00000000..a27c0a54 --- /dev/null +++ b/performance/menu-cache/run.sh @@ -0,0 +1,210 @@ +#!/bin/bash +# 메뉴 목록 조회 단계별 비교: 기존 N+1 → JOIN → JOIN+인덱스 → JOIN+인덱스+캐시. 결과는 performance/menu-cache/out/<이름>/ +# 기본은 쿠폰 프로젝트의 EC2 3대(db·app·k6, ~/Desktop/CouponToyProject/performance/ec2/README.md)를 Mac 에서 SSH 로만 다룬다 +# LOCAL=1 이면 Mac 한 대: MySQL·Redis 는 도커(EC2 db 처럼 각 1 CPU·1GB), 앱·k6 는 Mac. CPU 를 나눠 쓰니 절대값보다 단계 간 차이를 본다 +# JARS= run.sh setup jar·스크립트 올리기, 스키마·목업 데이터 만들기 +# run.sh verify 기존 코드와 JOIN 코드의 메뉴 목록 응답 비교(user·admin, 전 주점) + admin 목록 1회당 쿼리 수 +# run.sh bench <단계>... nplus1 | join | join-idx | cache. RATES 단계마다 측정 (손님 목록 조회 + 초당 WRITE_RATE 건 품절 토글) +# run.sh delay 부하 중 품절 토글 → 손님 조회 반영까지 걸린 시간 (cache) +# run.sh cleanup 앱 종료, nowait_lab 스키마 삭제 (LOCAL 은 컨테이너까지) +# STORES=200 RATES="200 500 1000" DURATION=60s WRITE_RATE=1 LIST_RATIO=1 DELAY_RATE=400 +set -u +cd "$(dirname "$0")/../.." +D=performance/menu-cache +STORES=${STORES:-200} +IDX=idx_menus_store_id_deleted_sort_order # Menu 엔티티의 @Index 와 같게 + +if [ "${LOCAL:-}" ]; then + WORK=$D/out/local; mkdir -p $WORK/logs + DB_PUB=local APP_PUB=local K6_PUB=local DB_PRIV=127.0.0.1 APP_PRIV=127.0.0.1 DB_PORT=13306 REDIS_PORT=16379 DB_PASSWORD=bench + MYSQL="docker exec -i -e MYSQL_PWD=$DB_PASSWORD nowait-bench-mysql mysql -uroot -h127.0.0.1" + on() { shift; (cd $WORK && bash -c "$*"); } + put() { shift; local f; for f in "${@:1:$#-1}"; do ln -sf "$(realpath "$f")" $WORK/; done; } + get() { cp $WORK/$2 $3; } + q() { $MYSQL -N nowait_lab -e "$1"; } + sqlf() { $MYSQL $1; } # 표준 입력의 SQL 실행 + mysql_usec() { docker exec nowait-bench-mysql cat /sys/fs/cgroup/cpu.stat | awk '$1=="usage_usec"{print $2}'; } +else + source ${HOSTS:-$HOME/Desktop/CouponToyProject/build/ec2/hosts.env} + DB_PORT=3306 REDIS_PORT=6379 + SSH_OPTS="-i $KEY -o StrictHostKeyChecking=accept-new -o LogLevel=ERROR -o ControlMaster=auto -o ControlPath=$HOME/.ssh/cm-%r@%h:%p -o ControlPersist=10m" + on() { local h=$1; shift; ssh $SSH_OPTS ubuntu@$h "$@"; } + put() { local h=$1; shift; scp -q $SSH_OPTS "${@:1:$#-1}" ubuntu@$h:"${@: -1}"; } + get() { scp -q $SSH_OPTS ubuntu@$1:"$2" "$3"; } + q() { on $DB_PUB "mysql -N nowait_lab -e \"$1\""; } + sqlf() { on $DB_PUB "mysql $1"; } + mysql_usec() { on $DB_PUB 'cat /sys/fs/cgroup/system.slice/docker-$(docker inspect -f "{{.Id}}" coupon-toy-mysql).scope/cpu.stat' | awk '$1=="usage_usec"{print $2}'; } +fi +now() { python3 -c 'import time; print(time.time())'; } + +# 운영 시작 스크립트와 같은 힙. SQL 로그는 끄고, 에러 알림이 팀 Discord 로 가지 않게 웹훅을 막는다 +APP_ARGS="--spring.datasource.url='jdbc:mysql://$DB_PRIV:$DB_PORT/nowait_lab?createDatabaseIfNotExist=true&serverTimezone=Asia/Seoul' \ + --spring.datasource.username=root --spring.datasource.password=$DB_PASSWORD \ + --spring.data.redis.host=$DB_PRIV --spring.data.redis.port=$REDIS_PORT --spring.data.redis.password= \ + --spring.jpa.show-sql=false --spring.jpa.properties.hibernate.show_sql=false \ + --discord.webhook.user-url=http://127.0.0.1:9 --discord.webhook.admin-url=http://127.0.0.1:9" +start() { # start <포트> <프로필> [추가 인자...] + local jar=$1 port=$2 profile=$3; shift 3 + on $APP_PUB "nohup java -Xms256m -Xmx512m -XX:+UseG1GC -Duser.timezone=Asia/Seoul -jar $jar --server.port=$port \ + --spring.profiles.active=$profile $APP_ARGS $* > logs/${jar%.jar}.log 2>&1 < /dev/null &" + local i; for i in $(seq 1 120); do on $APP_PUB "curl -sf -m 2 localhost:$port/health-check" > /dev/null && return 0; sleep 1; done + echo "$jar did not start"; on $APP_PUB "tail -30 logs/${jar%.jar}.log"; return 1 +} +# pkill·pgrep 패턴은 [j] [.] 로 써서 그 명령을 돌리는 원격 셸 자신과는 맞지 않게 한다 +running() { on $APP_PUB "pgrep -f '[j]ava .*${1%.jar}[.]jar' > /dev/null"; } +stop() { on $APP_PUB "pkill -f '[j]ava .*${1%.jar}[.]jar'; for i in \$(seq 1 30); do pgrep -f '[j]ava .*${1%.jar}[.]jar' > /dev/null || exit 0; sleep 1; done"; } +cpu_time() { on $APP_PUB "ps -o time= -p \$(pgrep -f '[j]ava .*${1%.jar}[.]jar')" | tr -d ' '; } # 그 JVM 의 누적 CPU 시간 + +cache_stats() { # hitmiss (캐시 없는 기존 코드는 빈 값) + on $APP_PUB "curl -s localhost:8081/nowait/management/prometheus" | awk '/^cache_gets_total\{/ && /cache="cache:menus"/ { + if ($0 ~ /result="hit"/) h=$2; else if ($0 ~ /result="miss"/) m=$2 } END { printf "%s\t%s", h, m }' +} +index() { # index on|off 앱(ddl-auto)이 만든 인덱스를 단계에 맞게 지우거나 다시 만든다 + q "DROP INDEX $IDX ON menus" 2>/dev/null + [ $1 = off ] || q "CREATE INDEX $IDX ON menus (store_id, deleted, sort_order)" +} + +setup() { + set -e + local J=${JARS:?JARS=} + if [ "${LOCAL:-}" ]; then + docker rm -f nowait-bench-mysql nowait-bench-redis > /dev/null 2>&1 || true + docker run -d --name nowait-bench-mysql --cpus=1 -m 1g -e MYSQL_ROOT_PASSWORD=$DB_PASSWORD -p $DB_PORT:3306 mysql:8.0.36 > /dev/null + docker run -d --name nowait-bench-redis --cpus=1 -m 1g -p $REDIS_PORT:6379 redis:7.2-alpine > /dev/null + until $MYSQL -e 'SELECT 1' > /dev/null 2>&1; do sleep 2; done + else + on $APP_PUB "command -v java >/dev/null || { sudo apt-get update -q && sudo apt-get install -yq openjdk-17-jre-headless; }; mkdir -p logs" + fi + put $APP_PUB $J/user-asis.jar $J/user-tobe.jar $J/admin-asis.jar $J/admin.jar $D/probe.sh $D/verify.sh . + put $K6_PUB $D/menu-load.js . + # 스키마는 앱이 만든다(ddl-auto: update). 두 앱이 동시에 만들면 FK 이름이 겹치므로 차례로 띄운다 + stop user-asis.jar; stop user-tobe.jar; stop admin-asis.jar; stop admin.jar + start user-tobe.jar 8081 user-local + start admin.jar 8085 admin-local + { echo "SET @stores = $STORES;"; cat $D/seed.sql; } | sqlf nowait_lab + on $APP_PUB "curl -s -X POST localhost:8085/admin/users/signup -H 'Content-Type: application/json' \ + -d '{\"email\":\"load@test.com\",\"password\":\"loadPass1!\",\"nickname\":\"loadtester\",\"socialType\":\"LOCAL\"}'"; echo + q "UPDATE users SET role = 'SUPER_ADMIN' WHERE email = 'load@test.com'" # 모든 주점의 admin 메뉴 목록을 볼 수 있게 + echo "stores menus menu_images users: $(q "SELECT (SELECT COUNT(*) FROM stores), (SELECT COUNT(*) FROM menus), (SELECT COUNT(*) FROM menu_images), (SELECT COUNT(*) FROM users)")" + stop user-tobe.jar +} + +verify() { + L=$D/out/verify; mkdir -p $L + running admin.jar || start admin.jar 8085 admin-local || return 1 + stop user-asis.jar; stop user-tobe.jar; stop admin-asis.jar + start user-asis.jar 8081 user-local && start user-tobe.jar 8082 user-local && start admin-asis.jar 8086 admin-local || return 1 + on $APP_PUB "STORES=$STORES bash verify.sh diff" | tee $L/diff.txt + local port; for port in 8086 8085; do + q "TRUNCATE TABLE performance_schema.events_statements_summary_by_digest" + on $APP_PUB "STORES=$STORES bash verify.sh admin $port" + echo "admin :$port 메뉴 목록 $STORES 회 — $(sqlf "-N nowait_lab" < $D/digest.sql | awk '{printf "%s %s건 ", $1, $2}')" + done | tee $L/admin-queries.txt + stop user-asis.jar; stop user-tobe.jar; stop admin-asis.jar +} + +level() { # level <이름> <초당 요청> <시간> + local name=$1 rate=$2 dur=$3 + q "TRUNCATE TABLE performance_schema.events_statements_summary_by_digest" + local c0=$(cache_stats) m0=$(mysql_usec) p0=$(cpu_time $JAR) t0=$(now) + on $K6_PUB "env USER_URL=http://$APP_PRIV:8081 ADMIN_URL=http://$APP_PRIV:8085 STORES=$STORES RATE=$rate DURATION=$dur \ + WRITE_RATE=${WRITE_RATE:-1} LIST_RATIO=${LIST_RATIO:-1} k6 run --quiet --no-color --summary-export k6-$name.json menu-load.js" > $L/$name.log 2>&1 + local ec=$? t1=$(now) c1=$(cache_stats) m1=$(mysql_usec) p1=$(cpu_time $JAR) + sqlf "-N nowait_lab" < $D/digest.sql > $L/$name.digest + get $K6_PUB k6-$name.json $L/$name.json + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' $name $rate "$c0" "$c1" $((m1 - m0)) $(python3 -c "print($t1 - $t0)") $p0 $p1 >> $L/runs.tsv + echo "$name k6_exit=$ec" +} + +bench() { + local cfg rate + for cfg in "$@"; do + local ttl= idx=on; JAR=user-tobe.jar + case $cfg in + nplus1) JAR=user-asis.jar idx=off ;; + join) ttl=0s idx=off ;; + join-idx) ttl=0s ;; + cache) ;; + *) echo "모르는 단계: $cfg"; return 1 ;; + esac + L=$D/out/$cfg; mkdir -p $L; : > $L/runs.tsv + running admin.jar || start admin.jar 8085 admin-local || return 1 + stop user-asis.jar; stop user-tobe.jar + q "UPDATE menus SET is_sold_out = 0" + start $JAR 8081 user-local ${ttl:+--nowait.cache.menu.ttl=$ttl} || return 1 + index $idx + level warm-$cfg 100 20s # 예열 (보고서에서 뺀다) + for rate in ${RATES:-200 500 1000}; do level $cfg-$rate $rate ${DURATION:-60s}; done + stop $JAR + report $L | tee $L/report.md + done +} + +report() { + python3 - "$1" <<'EOF' +import json, sys +L = sys.argv[1] +def secs(t): # ps 의 time: [D-]HH:MM:SS (Linux), [H:]M:SS.ss (macOS) + d, _, hms = t.rpartition('-') + s = 0.0 + for x in hms.split(':'): + s = s * 60 + float(x) + return s + int(d or 0) * 86400 +print('| 목표 req/s | 처리 req/s | p50 ms | p95 ms | p99 ms | 실패 | 못 보낸 요청 | 히트율 | stores | menus | menu_images | 요청당 쿼리 | 요청당 읽은 행 | MySQL CPU(코어) | user-api CPU(코어) |') +print('|' + '---|' * 15) +for line in open(f'{L}/runs.tsv'): + name, rate, h0, m0, h1, m1, mus, el, p0, p1 = line.rstrip('\n').split('\t') + if name.startswith('warm'): + continue + k = json.load(open(f'{L}/{name}.json'))['metrics'] + d, n = k['http_req_duration{scenario:reads}'], k['http_reqs{scenario:reads}'] + failed = k['http_req_failed{scenario:reads}'].get('value', 0) + dropped = k.get('dropped_iterations', {}).get('count', 0) + q = {t: (int(c), int(r)) for t, c, r, _ in (l.split('\t') for l in open(f'{L}/{name}.digest') if l.strip())} + hit, miss = float(h1 or 0) - float(h0 or 0), float(m1 or 0) - float(m0 or 0) + hr = f'{hit / (hit + miss) * 100:.1f}%' if hit + miss else '-' + calls = {t: q.get(t, (0, 0))[0] for t in ('stores', 'menus', 'menu_images')} + rows = sum(q.get(t, (0, 0))[1] for t in ('stores', 'menus', 'menu_images')) + el = float(el) + print(f"| {rate} | {n['rate']:.0f} | {d['med']:.1f} | {d['p(95)']:.1f} | {d['p(99)']:.1f} | {failed * 100:.2f}% | {dropped:.0f} " + f"| {hr} | {calls['stores']} | {calls['menus']} | {calls['menu_images']} | {sum(calls.values()) / n['count']:.2f} " + f"| {rows / n['count']:.0f} | {int(mus) / 1e6 / el:.2f} | {(secs(p1) - secs(p0)) / el:.2f} |") +EOF +} + +delay() { + L=$D/out/delay; mkdir -p $L + running admin.jar || start admin.jar 8085 admin-local || return 1 + stop user-asis.jar + running user-tobe.jar || start user-tobe.jar 8081 user-local || return 1 + index on + on $K6_PUB "nohup env USER_URL=http://$APP_PRIV:8081 ADMIN_URL=http://$APP_PRIV:8085 STORES=$STORES RATE=${DELAY_RATE:-400} DURATION=120s \ + WRITE_RATE=1 LIST_RATIO=${LIST_RATIO:-0.8} k6 run --quiet --no-color menu-load.js > delay-k6.log 2>&1 < /dev/null &" + sleep 15 + on $APP_PUB "N=${N:-30} bash probe.sh" | tee $L/probe.txt + on $K6_PUB "while pgrep -x k6 > /dev/null; do sleep 1; done" + python3 - $L/probe.txt <<'EOF' | tee $L/report.md +import statistics, sys +rows = [list(map(int, l.split())) for l in open(sys.argv[1]) if l.strip()] +toggle, reflect, stale = zip(*rows) +p = lambda xs, q: sorted(xs)[min(len(xs) - 1, int(len(xs) * q))] +print(f'토글 {len(rows)}회 | 토글 응답 p50 {p(toggle, .5)}ms p95 {p(toggle, .95)}ms ' + f'| 응답 후 반영 확인 p50 {p(reflect, .5)}ms p95 {p(reflect, .95)}ms 최대 {max(reflect)}ms ' + f'| 옛 값을 받은 조회 {sum(stale)}회') +EOF +} + +cleanup() { + stop user-asis.jar; stop user-tobe.jar; stop admin-asis.jar; stop admin.jar + if [ "${LOCAL:-}" ]; then docker rm -f nowait-bench-mysql nowait-bench-redis > /dev/null; else q "DROP DATABASE IF EXISTS nowait_lab"; fi +} + +case ${1:-} in + setup) setup ;; + verify) verify ;; + bench) shift; bench "$@" ;; + delay) delay ;; + report) report "$2" ;; + cleanup) cleanup ;; + *) sed -n 2,10p "$0"; exit 1 ;; +esac diff --git a/performance/menu-cache/seed.sql b/performance/menu-cache/seed.sql new file mode 100644 index 00000000..cb3dbf81 --- /dev/null +++ b/performance/menu-cache/seed.sql @@ -0,0 +1,25 @@ +-- 목업: 학과 5, 주점 @stores, 주점당 메뉴 20. 메뉴 id = (store_id - 1) * 20 + n, 공개 코드 = LOAD + 8자리 store_id +-- 이미지: 메뉴당 1장, 단 2번 메뉴는 2장·20번 메뉴는 없음 (JOIN 결과를 메뉴별로 묶는지, 이미지 없는 메뉴가 빠지지 않는지 확인용) +-- 실행 전에 SET @stores = <주점 수>; +SET SESSION cte_max_recursion_depth = 1000000; +DELETE FROM menu_images; +DELETE FROM menus; +DELETE FROM stores; +DELETE FROM departments; + +INSERT INTO departments (id, name) +WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < 5) +SELECT i, CONCAT('dept-', i) FROM n; + +INSERT INTO stores (store_id, created_at, deleted, department_id, is_active, name, public_code, location, description) +WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < @stores) +SELECT i, NOW(), 0, i % 5 + 1, 1, CONCAT('store-', i), CONCAT('LOAD', LPAD(i, 8, '0')), 'location', 'description' FROM n; + +INSERT INTO menus (id, created_at, admin_display_name, deleted, description, is_sold_out, name, price, sort_order, store_id) +WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < @stores * 20) +SELECT i, NOW(), CONCAT('menu-', i), 0, 'description', 0, CONCAT('menu-', i), 1000 * ((i - 1) % 20 + 1), (i - 1) % 20 + 1, (i - 1) DIV 20 + 1 FROM n; + +INSERT INTO menu_images (id, created_at, file_key, image_url, menu_id) +SELECT id, NOW(), CONCAT('menu/', id), CONCAT('https://img.example.com/menu/', id, '.png'), id FROM menus WHERE sort_order <> 20 +UNION ALL +SELECT id + @stores * 20, NOW(), CONCAT('menu/', id, '-2'), CONCAT('https://img.example.com/menu/', id, '-2.png'), id FROM menus WHERE sort_order = 2; diff --git a/performance/menu-cache/verify.sh b/performance/menu-cache/verify.sh new file mode 100644 index 00000000..72eb4e57 --- /dev/null +++ b/performance/menu-cache/verify.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# app 인스턴스에서 실행 +# diff 기존(user :8081, admin :8086)과 JOIN(user :8082, admin :8085)의 메뉴 목록 응답을 전 주점 비교 +# admin <포트> 그 admin 으로 전 주점 메뉴 목록을 한 번씩 조회 (쿼리 수 집계용) +T=$(curl -s -X POST localhost:8085/admin/users/login -H 'Content-Type: application/json' \ + -d '{"email":"load@test.com","password":"loadPass1!"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["response"]["body"]["accessToken"])') +user() { curl -s localhost:$1/v1/stores/LOAD$(printf %08d $2)/menus; } +admin() { curl -s localhost:$1/admin/menus/all-menus/stores/$2 -H "Authorization: Bearer $T"; } +case $1 in + diff) + for kind in user admin; do + if [ $kind = user ]; then old=8081 new=8082; else old=8086 new=8085; fi + same=0 diff=0 + for s in $(seq $STORES); do + a=$($kind $old $s) b=$($kind $new $s) + if [ "$a" = "$b" ] && [[ $a == *'"menuId"'* ]]; then same=$((same + 1)); continue; fi + diff=$((diff + 1)) + [ $diff = 1 ] && printf '%s 주점 %s 다름\n기존: %.600s\nJOIN: %.600s\n' $kind $s "$a" "$b" + done + echo "$kind 메뉴 목록: 같음 $same / 다름 $diff" + done ;; + admin) for s in $(seq $STORES); do admin $2 $s > /dev/null; done ;; +esac