diff --git a/.github/workflows/be-cd-prod.yml b/.github/workflows/be-cd-prod.yml index 4c50ea63e..033d0e9f7 100644 --- a/.github/workflows/be-cd-prod.yml +++ b/.github/workflows/be-cd-prod.yml @@ -8,104 +8,122 @@ on: paths: - backend/** -env: - ARTIFACT_NAME: app-artifact - ARTIFACT_PATH: ./artifact - JAR_NAME: app.jar - jobs: build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - outputs: - artifact_name: ${{ env.ARTIFACT_NAME }} - jar_name: ${{ env.JAR_NAME }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setting prod-secret.yml - run: | - echo "${{ secrets.PROD_SECRET_YML }}" > ./src/main/resources/prod-secret.yml - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - name: BootJar with Gradle - run: ./gradlew bootJar - - - name: Move artifact file - run: mv build/libs/*.jar ${{ env.ARTIFACT_PATH }}/${{ env.JAR_NAME }} - - - name: Copy scripts file - run: cp scripts/ ${{ env.ARTIFACT_PATH }}/ - - - name: Upload artifact file - uses: actions/upload-artifact@v4 - with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACT_PATH }} + uses: ./.github/workflows/build.yml + with: + profile: prod + secrets: + secret_yml: ${{ secrets.PROD_SECRET_YML }} deploy-a: - needs: build - uses: ./.github/workflows/self-hosted-runner-deployment.yml + needs: [ build ] + uses: ./.github/workflows/blue-green.yml with: - self_hosted_runner: prod-c # 추후 변경 prod-a + self_hosted_runner: prod-a artifact_name: ${{ needs.build.outputs.artifact_name }} jar_name: ${{ needs.build.outputs.jar_name }} profile: prod + app_path: ~/app deploy-b: - needs: build - uses: ./.github/workflows/self-hosted-runner-deployment.yml + needs: [ build ] + uses: ./.github/workflows/blue-green.yml with: - self_hosted_runner: prod-d # 추후 변경 prod-b + self_hosted_runner: prod-b artifact_name: ${{ needs.build.outputs.artifact_name }} jar_name: ${{ needs.build.outputs.jar_name }} profile: prod + app_path: ~/app + + rollback-a: + name: "[Failure] Rollback Deploy A" + needs: [ deploy-a, deploy-b ] + if: failure() && (needs.deploy-a.result == 'failure' || needs.deploy-b.result == 'failure') + uses: ./.github/workflows/shutdown.yml + with: + self_hosted_runner: prod-a + port: ${{ needs.deploy-a.outputs.green_port }} + + rollback-b: + name: "[Failure] Rollback Deploy B" + needs: [ deploy-a, deploy-b ] + if: failure() && (needs.deploy-a.result == 'failure' || needs.deploy-b.result == 'failure') + uses: ./.github/workflows/shutdown.yml + with: + self_hosted_runner: prod-b + port: ${{ needs.deploy-b.outputs.green_port }} - error-handling: + deploy-failure-notification: + name: "[Failure] Deploy Failure Notification" needs: [ deploy-a, deploy-b ] + if: failure() && (needs.deploy-a.result == 'failure' || needs.deploy-b.result == 'failure') runs-on: ubuntu-latest - if: ${{ failure() }} steps: - - name: Debug - run: echo "Deploy failed" # 추후 수정 + green 롤백 + 디스코드 알림 + - name: Send notification to Discord # todo + run: echo "테스트입니다" - change-nginx-config-a: - needs: [ deploy-a, error-handling] - uses: ./.github/workflows/change_nginx_config.yml + configure-nginx-a: + needs: [ deploy-a, deploy-b ] + uses: ./.github/workflows/nginx-port-forwarding.yml with: - self_hosted_runner: prod-c # 추후 변경 prod-a - green_port: ${{ needs.deploy-a.outputs.green_port }} - - change-nginx-config-b: - needs: [ deploy-b, error-handling] - uses: ./.github/workflows/change_nginx_config.yml + self_hosted_runner: prod-a + app_path: ~/app + old_port: ${{ needs.deploy-a.outputs.blue_port }} + new_port: ${{ needs.deploy-a.outputs.green_port }} + + configure-nginx-b: + needs: [ deploy-a, deploy-b ] + uses: ./.github/workflows/nginx-port-forwarding.yml with: - self_hosted_runner: prod-d # 추후 변경 prod-b - green_port: ${{ needs.deploy-b.outputs.green_port }} - - on-failure: - needs: [change-nginx-config-a, change-nginx-config-b] - runs-on: [ prod-c, prod-d ] # 추후 변경 prod-a, prod-b - if: failure() + self_hosted_runner: prod-b + app_path: ~/app + old_port: ${{ needs.deploy-b.outputs.blue_port }} + new_port: ${{ needs.deploy-b.outputs.green_port }} + + rollback-nginx-a: + name: "[Failure] Rollback Nginx A" + needs: [ configure-nginx-a, configure-nginx-b ] + if: failure() && (needs.configure-nginx-a.result == 'failure' || needs.configure-nginx-b.result == 'failure') + uses: ./.github/workflows/nginx-port-forwarding.yml + with: + self_hosted_runner: prod-a + app_path: ~/app + old_port: ${{ needs.configure-nginx-a.outputs.new_port }} + new_port: ${{ needs.configure-nginx-a.outputs.old_port }} + old_shutdown: true + + rollback-nginx-b: + name: "[Failure] Rollback Nginx B" + needs: [ configure-nginx-a, configure-nginx-b ] + if: failure() && (needs.configure-nginx-a.result == 'failure' || needs.configure-nginx-b.result == 'failure') + uses: ./.github/workflows/nginx-port-forwarding.yml + with: + self_hosted_runner: prod-b + app_path: ~/app + old_port: ${{ needs.configure-nginx-b.outputs.new_port }} + new_port: ${{ needs.configure-nginx-b.outputs.old_port }} + old_shutdown: true + + configure-nginx-faliure-notification: + name: "[Failure] Nginx Failure Notification" + needs: [ configure-nginx-a, configure-nginx-b ] + if: failure() && (needs.configure-nginx-a.result == 'failure' || needs.configure-nginx-b.result == 'failure') + runs-on: ubuntu-latest steps: - - name: Rollback - run: | - sudo kill -15 $(sudo lsof -t -i: ${{ needs.deploy-a.outputs.green_port }}) + - name: Send notification to Discord # todo + run: echo "테스트입니다" + + blue-shutdown-a: + needs: [ configure-nginx-a, configure-nginx-b ] + uses: ./.github/workflows/shutdown.yml + with: + self_hosted_runner: prod-a + port: ${{ needs.configure-nginx-a.outputs.old_port }} - on-success: - needs: [change-nginx-config-a, change-nginx-config-b] - runs-on: [ prod-c, prod-d ] # 추후 변경 prod-a, prod-b - if: success() - steps: - - name: Blue Down - run: | - sudo kill -15 $(sudo lsof -t -i: 8080) # todo blue port + blue-shutdown-b: + needs: [ configure-nginx-a, configure-nginx-b ] + uses: ./.github/workflows/shutdown.yml + with: + self_hosted_runner: prod-b + port: ${{ needs.configure-nginx-b.outputs.old_port }} diff --git a/.github/workflows/blue-green.yml b/.github/workflows/blue-green.yml new file mode 100644 index 000000000..f2d4648cb --- /dev/null +++ b/.github/workflows/blue-green.yml @@ -0,0 +1,60 @@ +name: Blue Green Deployment + +on: + workflow_call: + inputs: + self_hosted_runner: + description: 'self hosted runner label' + required: true + type: string + artifact_name: + description: 'uploaded artifact name' + required: true + type: string + jar_name: + description: 'uploaded jar name' + required: true + type: string + profile: + description: 'profile' + required: true + type: string + app_path: + description: 'app path' + required: true + type: string + outputs: + green_port: + value: ${{ jobs.deploy-green.outputs.green_port }} + blue_port: + value: ${{ jobs.deploy-green.outputs.blue_port }} + +jobs: + deploy-green: + runs-on: ${{ inputs.self_hosted_runner }} + outputs: + green_port: ${{ steps.blue_green_port.outputs.green_port }} + blue_port: ${{ steps.blue_green_port.outputs.blue_port }} + steps: + - name: Download artifact file + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact_name }} + path: ${{ inputs.app_path }} + + - name: Change permission of shell script + run: chmod +x ${{ inputs.app_path }}/*.sh + + - name: Get blue green port + id: blue_green_port + run: ${{ inputs.app_path }}/get_blue_green_port.sh | awk '{print $0}' >> $GITHUB_OUTPUT + + - name: Run green java application in ${{ inputs.self_hosted_runner }} + run: sudo nohup java -Dspring.profiles.active=${{ inputs.profile }} -Dserver.port=${{ steps.blue_green_port.outputs.green_port }} -Duser.timezone=Asia/Seoul -jar ${{ inputs.app_path }}/${{ inputs.jar_name }} & + + health_check: + needs: [ deploy-green ] + runs-on: ${{ inputs.self_hosted_runner }} + steps: + - name: Health check green + run: ${{ inputs.app_path }}/green_health_check.sh ${{ needs.deploy-green.outputs.green_port }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..2d5ebb699 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,68 @@ +name: backend build jar file and upload artifact file + +on: + workflow_call: + inputs: + profile: + description: 'profile' + required: true + type: string + artifact_name: + description: 'artifact name' + default: 'app-artifact' + required: false + type: string + jar_name: + description: 'jar name' + default: 'app.jar' + required: false + type: string + gradlew_options: + description: 'gradle options' + required: false + type: string + secrets: + secret_yml: + description: 'secret yml' + required: true + outputs: + artifact_name: + value: ${{ inputs.artifact_name }} + jar_name: + value: ${{ inputs.jar_name }} + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./backend + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setting ${{ inputs.profile }}-secret.yml + run: | + echo "${{ secrets.secret_yml }}" > ./src/main/resources/${{ inputs.profile }}-secret.yml + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: BootJar with Gradle + run: ./gradlew bootJar ${{ inputs.gradlew_options }} + + - name: Move artifact file # todo script 환경 분리 + run: | + mkdir -p ${{ inputs.artifact_name }} && \ + mv build/libs/*.jar ${{ inputs.artifact_name }}/${{ inputs.jar_name }} && \ + mv scripts/* ${{ inputs.artifact_name }}/ + + - name: Upload artifact file + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact_name }} + path: ./backend/${{ inputs.artifact_name }} diff --git a/.github/workflows/change_nginx_config.yml b/.github/workflows/change_nginx_config.yml deleted file mode 100644 index 09c7cb41a..000000000 --- a/.github/workflows/change_nginx_config.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Nginx Config Change - -on: - workflow_call: - inputs: - self_hosted_runner: - description: '배포할 self-hosted 러너 이름' - required: true - type: string - green_port: - description: '그린 포트' - required: true - type: string - -env: - default_path: ~/app - -jobs: - change-nginx-config: - runs-on: ${{ inputs.self_hosted_runner }} - steps: - - name: Change Nginx Config - run: | - ./${{ env.default_path }}/change_nginx_config.sh ${{ inputs.green_port }} - # actuator 포트 변경 보류 - # Nginx Config 변경 - # Nginx 재시작 - # Nginx Config 변경 확인 diff --git a/.github/workflows/nginx-port-forwarding.yml b/.github/workflows/nginx-port-forwarding.yml new file mode 100644 index 000000000..313e9b7da --- /dev/null +++ b/.github/workflows/nginx-port-forwarding.yml @@ -0,0 +1,48 @@ +name: Update Nginx Port Forwarding + +on: + workflow_call: + inputs: + self_hosted_runner: + description: 'self hosted runner label' + required: true + type: string + app_path: + description: 'app path' + required: true + type: string + old_port: + description: 'old port' + required: true + type: string + new_port: + description: 'new port for Nginx port forwarding' + required: true + type: string + old_shutdown: + description: 'old shutdown flag' + required: false + type: boolean + outputs: + old_port: + value: ${{ inputs.old_port }} + new_port: + value: ${{ inputs.new_port }} + +jobs: + old-shutdown: + if: ${{ inputs.old_shutdown }} + uses: ./.github/workflows/shutdown.yml + with: + self_hosted_runner: ${{ inputs.self_hosted_runner }} + port: ${{ inputs.old_port }} + + update-nginx-port-forwarding: + name: update + runs-on: ${{ inputs.self_hosted_runner }} + steps: + - name: Update Nginx port forwarding from ${{ inputs.old_port }} to ${{ inputs.new_port }} + run: ${{ inputs.app_path }}/change_nginx_port_forwarding.sh ${{ inputs.new_port }} + + - name: Reload Nginx + run: sudo nginx -s reload # todo 에러 처리 diff --git a/.github/workflows/self-hosted-runner-deployment.yml b/.github/workflows/self-hosted-runner-deployment.yml deleted file mode 100644 index 31eb8486e..000000000 --- a/.github/workflows/self-hosted-runner-deployment.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Self-hosted Runner Deployment - -on: - workflow_call: - inputs: - self_hosted_runner: - description: '배포할 self-hosted 러너 이름' - required: true - type: string - artifact_name: - description: 'artifact 이름' - required: true - type: string - jar_name: - description: '배포할 jar 파일 이름' - required: true - type: string - profile: - description: '배포할 프로파일' - required: true - type: string - outputs: - green_port: - description: '그린 포트' - value: ${{ jobs.deploy.outputs.port }} - -env: - default_path: ~/app - -jobs: - deploy: - runs-on: ${{ inputs.self_hosted_runner }} - outputs: - port: ${{ steps.green-port.outputs.port }} - steps: - - name: Download artifact file - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.artifact_name }} - path: ${{ env.default_path }} - - - name: Get Green Port - id: green-port - run: | - ./${{ env.default_path }}/get_green_port.sh | awk '{print "port=$0"}' >> $GITHUB_OUTPUT - - - name: Start Java Application - run: sudo nohup java --Dspring.profiles.active=${{ inputs.profile }} --Dserver.port=${{ steps.green-port.outputs.port }} --Duser.timezone=Asia/Seoul -jar ${{ env.default_path }}/${{ inputs.jar_name }} & - - health-check: - needs: deploy - runs-on: ${{ inputs.self_hosted_runner }} - steps: - - name: Health Check - run: | - ./${{ env.default_path }}/green_health_check.sh ${{ needs.deploy.outputs.port }} - - debug: - needs: health-check - runs-on: ubuntu-latest - if: ${{ failure() }} - steps: - - name: Debug - run: echo "Deploy failed" # 추후 수정 diff --git a/.github/workflows/shutdown.yml b/.github/workflows/shutdown.yml new file mode 100644 index 000000000..2e7ee4c88 --- /dev/null +++ b/.github/workflows/shutdown.yml @@ -0,0 +1,20 @@ +name: Shutdown Server + +on: + workflow_call: + inputs: + self_hosted_runner: + description: 'self hosted runner label' + required: true + type: string + port: + description: 'shutdown port' + required: true + type: string + +jobs: + shutdown: + runs-on: ${{ inputs.self_hosted_runner }} + steps: + - name: Shutdown server - port ${{ inputs.port }} + run: sudo lsof -t -i:${{ inputs.port }} | xargs --no-run-if-empty sudo kill -15 diff --git a/backend/scripts/change_nginx_port_forwarding.sh b/backend/scripts/change_nginx_port_forwarding.sh new file mode 100755 index 000000000..3729ffbba --- /dev/null +++ b/backend/scripts/change_nginx_port_forwarding.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +NGINX_CONFIG="/etc/nginx/sites-available/default" +NEW_PORT=$1 + +if [ -z "$NEW_PORT" ]; then + echo "Invalid new port : $NEW_PORT" + exit 1 +fi + +OLD_PORT=$(sudo grep -oP '127.0.0.1:\K\d+' "$NGINX_CONFIG") + +if [ -n "$OLD_PORT" ]; then + sudo sed -i "s/127.0.0.1:$OLD_PORT/127.0.0.1:$NEW_PORT/g" "$NGINX_CONFIG" + echo "Switching from $OLD_PORT to $NEW_PORT" +else + echo "Can't find port forwarding format in nginx configuration" + exit 1 +fi diff --git a/backend/scripts/change_nginx_port_fowarding.sh b/backend/scripts/change_nginx_port_fowarding.sh deleted file mode 100644 index 04075ee79..000000000 --- a/backend/scripts/change_nginx_port_fowarding.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -e - -GREEN_PORT=$1 - -# todo -echo "GREEN_PORT : $GREEN_PORT" diff --git a/backend/scripts/get_green_port.sh b/backend/scripts/get_blue_green_port.sh old mode 100644 new mode 100755 similarity index 56% rename from backend/scripts/get_green_port.sh rename to backend/scripts/get_blue_green_port.sh index 4c9a6440b..9f18cfca0 --- a/backend/scripts/get_green_port.sh +++ b/backend/scripts/get_blue_green_port.sh @@ -1,28 +1,22 @@ #!/bin/bash -set -e - +# 현재 BLUE 호스트 확인 (8080 or 8081) NGINX_CONFIG="/etc/nginx/sites-available/default" -BLUE_PORT="" -GREEN_PORT="" - -# 현재 BLUE 호스트 확인 (8080 또는 8081) BLUE_PORT=$(sudo grep -oP '127\.0\.0\.1:\K(8080|8081)' "$NGINX_CONFIG") # 오류 처리: BLUE 호스트를 찾을 수 없을 경우 if [ -z "$BLUE_PORT" ]; then echo "Unable to determine the current BLUE host from NGINX configuration." - echo "blue host : $BLUE_PORT" + echo "Invalid blue port : $BLUE_PORT" exit 1 fi +# 환경 변수 설정 if [ "$BLUE_PORT" = "8080" ]; then - GREEN_PORT="8081" -elif [ "$BLUE_PORT" = "8081" ]; then - GREEN_PORT="8080" + + echo "blue_port=8080" + echo "green_port=8081" else - echo "Invalid BLUE_PORT value: $BLUE_PORT" - exit 1 + echo "blue_port=8081" + echo "green_port=8080" fi - -echo "$GREEN_PORT" diff --git a/backend/scripts/green_health_check.sh b/backend/scripts/green_health_check.sh old mode 100644 new mode 100755 index 97d611e1c..d040ebc07 --- a/backend/scripts/green_health_check.sh +++ b/backend/scripts/green_health_check.sh @@ -1,7 +1,5 @@ #!/bin/bash -set -e - GREEN_PORT=$1 health_check_process() { @@ -23,8 +21,7 @@ health_check_process() { check_health() { # HEALTH_URL 생성 - # local health_url="http://localhost:$GREEN_PORT/actuator/health" - local health_url="http://localhost:$GREEN_PORT" + local health_url="http://localhost:$GREEN_PORT/act-ddangkong/health" # 헬스 체크 실시 local response=$(curl -s --connect-timeout 5 -o /dev/null -w "%{http_code}" "$health_url") @@ -51,4 +48,4 @@ if health_check_process; then else echo "Exiting due to health check failure." exit 1 -fi \ No newline at end of file +fi diff --git a/backend/src/docs/asciidoc/room.adoc b/backend/src/docs/asciidoc/room.adoc index c45037af3..b2c8bc054 100644 --- a/backend/src/docs/asciidoc/room.adoc +++ b/backend/src/docs/asciidoc/room.adoc @@ -98,6 +98,10 @@ include::{snippets}/room/leave/path-parameters.adoc[] include::{snippets}/room/leave/http-response.adoc[] +response cookies + +include::{snippets}/room/leave/response-cookies.adoc[] + ''' === 방 설정 변경 diff --git a/backend/src/main/java/ddangkong/controller/room/RoomController.java b/backend/src/main/java/ddangkong/controller/room/RoomController.java index 76e31a17f..905394374 100644 --- a/backend/src/main/java/ddangkong/controller/room/RoomController.java +++ b/backend/src/main/java/ddangkong/controller/room/RoomController.java @@ -83,8 +83,11 @@ public RoomJoinResponse joinRoom(@PathVariable String uuid, @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/balances/rooms/{roomId}/members/{memberId}") public void leaveRoom(@PathVariable @Positive Long roomId, - @PathVariable @Positive Long memberId) { + @PathVariable @Positive Long memberId, + HttpServletRequest request, + HttpServletResponse response) { roomFacade.leaveRoom(roomId, memberId); + deleteCookie(request, response); } @ResponseStatus(HttpStatus.NO_CONTENT) @@ -130,4 +133,11 @@ private void setEncryptCookie(HttpServletRequest request, ResponseCookie encodedCookie = roomMemberCookieEncryptor.getEncodedCookie(cookieValue, origin); response.addHeader(HttpHeaders.SET_COOKIE, encodedCookie.toString()); } + + private void deleteCookie(HttpServletRequest request, + HttpServletResponse response) { + String origin = request.getHeader(HttpHeaders.ORIGIN); + ResponseCookie deleteCookie = roomMemberCookieEncryptor.deleteCookie(origin); + response.addHeader(HttpHeaders.SET_COOKIE, deleteCookie.toString()); + } } diff --git a/backend/src/main/java/ddangkong/controller/room/RoomMemberCookieEncryptor.java b/backend/src/main/java/ddangkong/controller/room/RoomMemberCookieEncryptor.java index e313870ea..f2377e348 100644 --- a/backend/src/main/java/ddangkong/controller/room/RoomMemberCookieEncryptor.java +++ b/backend/src/main/java/ddangkong/controller/room/RoomMemberCookieEncryptor.java @@ -1,6 +1,5 @@ package ddangkong.controller.room; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.server.Cookie.SameSite; import org.springframework.http.ResponseCookie; @@ -31,6 +30,16 @@ public ResponseCookie getEncodedCookie(Object value, String origin) { .build(); } + public ResponseCookie deleteCookie(String origin) { + return ResponseCookie.from(rejoinKey, null) + .httpOnly(true) + .secure(true) + .path(DEFAULT_PATH) + .sameSite(getSameSiteOption(origin)) + .maxAge(0) + .build(); + } + private String getSameSiteOption(String origin) { if (origin != null && origin.startsWith(LOCALHOST)) { return SameSite.NONE.attributeValue(); diff --git a/backend/src/main/java/ddangkong/domain/room/member/Member.java b/backend/src/main/java/ddangkong/domain/room/member/Member.java index 67496e63d..81e80ad15 100644 --- a/backend/src/main/java/ddangkong/domain/room/member/Member.java +++ b/backend/src/main/java/ddangkong/domain/room/member/Member.java @@ -15,13 +15,13 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.apache.logging.log4j.util.Strings; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class Member { - private static final int NICKNAME_MIN_LENGTH = 2; private static final int NICKNAME_MAX_LENGTH = 12; @Id @@ -46,8 +46,8 @@ private Member(String nickname, Room room, boolean isMaster) { } private void validateNickname(String nickname) { - if (nickname.length() < NICKNAME_MIN_LENGTH || nickname.length() > NICKNAME_MAX_LENGTH) { - throw new InvalidNicknameException(NICKNAME_MIN_LENGTH, NICKNAME_MAX_LENGTH); + if (Strings.isBlank(nickname) || nickname.length() > NICKNAME_MAX_LENGTH) { + throw new InvalidNicknameException(NICKNAME_MAX_LENGTH); } } diff --git a/backend/src/main/java/ddangkong/exception/ClientErrorCode.java b/backend/src/main/java/ddangkong/exception/ClientErrorCode.java index e8cbce480..8f5f49bfb 100644 --- a/backend/src/main/java/ddangkong/exception/ClientErrorCode.java +++ b/backend/src/main/java/ddangkong/exception/ClientErrorCode.java @@ -35,7 +35,7 @@ public enum ClientErrorCode { NOT_EXIST_COMMON("일반 멤버가 존재하지 않습니다."), EXCEED_MAX_MEMBER_COUNT("방의 최대 인원을 초과했습니다. 현재 멤버 수: %d"), NOT_ROOM_MEMBER("방에 존재하지 않는 멤버입니다."), - INVALID_NICKNAME("닉네임은 최소 %d글자, 최대 %d글자여야 합니다."), + INVALID_NICKNAME("닉네임은 공백이어선 안되며 최대 %d글자여야 합니다."), INVALID_MEMBER_ID("해당 ID에 일치하는 멤버가 없습니다."), // RoomContent diff --git a/backend/src/main/java/ddangkong/exception/room/member/InvalidNicknameException.java b/backend/src/main/java/ddangkong/exception/room/member/InvalidNicknameException.java index 8cf27bc15..9c7ebe1c4 100644 --- a/backend/src/main/java/ddangkong/exception/room/member/InvalidNicknameException.java +++ b/backend/src/main/java/ddangkong/exception/room/member/InvalidNicknameException.java @@ -6,8 +6,8 @@ public class InvalidNicknameException extends BadRequestException { - public InvalidNicknameException(int minLength, int maxLength) { - super(INVALID_NICKNAME.getMessage().formatted(minLength, maxLength)); + public InvalidNicknameException(int maxLength) { + super(INVALID_NICKNAME.getMessage().formatted(maxLength)); } @Override diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml index a6b1f25ee..c2de543ac 100644 --- a/backend/src/main/resources/application-prod.yml +++ b/backend/src/main/resources/application-prod.yml @@ -1,25 +1,17 @@ spring: config: import: prod-secret.yml - datasource: source: driver-class-name: com.mysql.cj.jdbc.Driver username: ${secret.datasource.source.username} password: ${secret.datasource.source.password} jdbc-url: jdbc:mysql://${secret.datasource.source.host}:${secret.datasource.source.port}/${secret.datasource.database}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false - hikari: - minimum-idle: ${secret.datasource.hikari.minimum-idle} - - replica: driver-class-name: com.mysql.cj.jdbc.Driver username: ${secret.datasource.replica.username} password: ${secret.datasource.replica.password} jdbc-url: jdbc:mysql://${secret.datasource.replica.host}:${secret.datasource.replica.port}/${secret.datasource.database}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false - hikari: - minimum-idle: ${secret.datasource.hikari.minimum-idle} - sql: init: @@ -34,11 +26,6 @@ spring: jdbc: batch_size: 1000 - task: - execution: - pool: - core-size: ${secret.spring.task.execution.pool.core-size} - server: port: ${secret.application.port} tomcat: @@ -62,9 +49,6 @@ logging: location: ${secret.application.log.location} management: - server: - port: ${secret.actuator.port} - endpoints: enabled-by-default: false web: diff --git a/backend/src/test/java/ddangkong/controller/room/RoomControllerTest.java b/backend/src/test/java/ddangkong/controller/room/RoomControllerTest.java index 2be367756..194262ad5 100644 --- a/backend/src/test/java/ddangkong/controller/room/RoomControllerTest.java +++ b/backend/src/test/java/ddangkong/controller/room/RoomControllerTest.java @@ -1,6 +1,7 @@ package ddangkong.controller.room; import static ddangkong.support.fixture.MemberFixture.PRIN; +import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -370,5 +371,34 @@ class 쿠키 { // then assertThat(body.nickname()).isEqualTo(roomJoinResponse.member().nickname()); } + + @Test + void 방을_나가면_쿠키를_삭제한다() { + // given + RoomJoinRequest body = new RoomJoinRequest("참가자"); + String cookie = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(body) + .when().post("/api/balances/rooms") + .getCookie("test_cookie"); + + RoomJoinResponse roomJoinResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .cookie("test_cookie", cookie) + .when().get("/api/balances/rooms/member") + .then().contentType(ContentType.JSON).log().all() + .statusCode(200) + .extract().as(RoomJoinResponse.class); + + // when + String deleteCookie = RestAssured.given().log().all() + .pathParam("roomId", roomJoinResponse.roomId()) + .pathParam("memberId", roomJoinResponse.member().memberId()) + .cookie("test_cookie", cookie) + .when().delete("/api/balances/rooms/{roomId}/members/{memberId}") + .getCookie("test_cookie"); + + assertThat(deleteCookie).isBlank(); + } } } diff --git a/backend/src/test/java/ddangkong/documentation/room/RoomDocumentationTest.java b/backend/src/test/java/ddangkong/documentation/room/RoomDocumentationTest.java index 97249127c..7ea92311e 100644 --- a/backend/src/test/java/ddangkong/documentation/room/RoomDocumentationTest.java +++ b/backend/src/test/java/ddangkong/documentation/room/RoomDocumentationTest.java @@ -271,6 +271,9 @@ class 방_나가기 { pathParameters( parameterWithName("roomId").description("방 ID"), parameterWithName("memberId").description("멤버 ID") + ), + responseCookies( + cookieWithName("test_cookie").description("삭제 쿠키") ) )); } diff --git a/backend/src/test/java/ddangkong/domain/room/member/MemberTest.java b/backend/src/test/java/ddangkong/domain/room/member/MemberTest.java index 7b96e43f3..60a408653 100644 --- a/backend/src/test/java/ddangkong/domain/room/member/MemberTest.java +++ b/backend/src/test/java/ddangkong/domain/room/member/MemberTest.java @@ -66,7 +66,7 @@ class 일반_유저인지_확인 { assertThat(actual).isFalse(); } } - + @Nested class 닉네임_검증 { @@ -78,7 +78,7 @@ class 닉네임_검증 { } @ParameterizedTest - @ValueSource(strings = {"0", "", "0123456789123"}) + @ValueSource(strings = {" ", "", "0123456789123"}) void 닉네임_길이가_유효하지_않는_경우_예외를_발생시킨다(String name) { // when & then assertThatThrownBy(() -> Member.createMaster(name, ROOM)) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4f6ba7ed8..93cf6472c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@sentry/react": "^8.24.0", "@tanstack/react-query": "^5.51.1", "@tanstack/react-query-devtools": "^5.51.23", + "copy-webpack-plugin": "^12.0.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-qr-code": "^2.0.15", @@ -3886,7 +3887,6 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4009,7 +4009,6 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4021,7 +4020,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4029,7 +4027,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4578,7 +4575,6 @@ }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6948,7 +6944,6 @@ }, "node_modules/@types/eslint": { "version": "8.56.10", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", @@ -6957,7 +6952,6 @@ }, "node_modules/@types/eslint-scope": { "version": "3.7.7", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint": "*", @@ -6966,7 +6960,6 @@ }, "node_modules/@types/estree": { "version": "1.0.5", - "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -7067,7 +7060,6 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -7100,7 +7092,6 @@ }, "node_modules/@types/node": { "version": "20.14.10", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -7506,7 +7497,6 @@ }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -7515,22 +7505,18 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.6", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.12.1", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", @@ -7540,12 +7526,10 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.6", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -7556,7 +7540,6 @@ }, "node_modules/@webassemblyjs/ieee754": { "version": "1.11.6", - "dev": true, "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -7564,7 +7547,6 @@ }, "node_modules/@webassemblyjs/leb128": { "version": "1.11.6", - "dev": true, "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" @@ -7572,12 +7554,10 @@ }, "node_modules/@webassemblyjs/utf8": { "version": "1.11.6", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -7592,7 +7572,6 @@ }, "node_modules/@webassemblyjs/wasm-gen": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -7604,7 +7583,6 @@ }, "node_modules/@webassemblyjs/wasm-opt": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -7615,7 +7593,6 @@ }, "node_modules/@webassemblyjs/wasm-parser": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -7628,7 +7605,6 @@ }, "node_modules/@webassemblyjs/wast-printer": { "version": "1.12.1", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -7678,12 +7654,10 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "dev": true, "license": "Apache-2.0" }, "node_modules/@yarnpkg/fslib": { @@ -7739,7 +7713,6 @@ }, "node_modules/acorn": { "version": "8.12.1", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7759,7 +7732,6 @@ }, "node_modules/acorn-import-attributes": { "version": "1.9.5", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8" @@ -7810,7 +7782,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -7825,7 +7796,6 @@ }, "node_modules/ajv-formats": { "version": "2.1.1", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -7841,7 +7811,6 @@ }, "node_modules/ajv-formats/node_modules/ajv": { "version": "8.16.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -7856,12 +7825,10 @@ }, "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/ajv-keywords": { "version": "3.5.2", - "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -8679,7 +8646,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -8694,7 +8660,6 @@ }, "node_modules/browserslist": { "version": "4.23.2", - "dev": true, "funding": [ { "type": "opencollective", @@ -8765,7 +8730,6 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "dev": true, "license": "MIT" }, "node_modules/bundle-name": { @@ -8843,7 +8807,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001641", - "dev": true, "funding": [ { "type": "opencollective", @@ -9003,7 +8966,6 @@ }, "node_modules/chrome-trace-event": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0" @@ -9331,7 +9293,6 @@ }, "node_modules/commander": { "version": "2.20.3", - "dev": true, "license": "MIT" }, "node_modules/common-path-prefix": { @@ -9467,6 +9428,130 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/core-js": { "version": "3.37.1", "dev": true, @@ -10655,7 +10740,6 @@ }, "node_modules/electron-to-chromium": { "version": "1.4.825", - "dev": true, "license": "ISC" }, "node_modules/emittery": { @@ -10716,7 +10800,6 @@ }, "node_modules/enhanced-resolve": { "version": "5.17.0", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -10904,7 +10987,6 @@ }, "node_modules/es-module-lexer": { "version": "1.5.4", - "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -11007,7 +11089,6 @@ }, "node_modules/escalade": { "version": "3.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11516,7 +11597,6 @@ }, "node_modules/eslint-scope": { "version": "5.1.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -11789,7 +11869,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -11800,7 +11879,6 @@ }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.3.0", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -11808,7 +11886,6 @@ }, "node_modules/estraverse": { "version": "4.3.0", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -11851,7 +11928,6 @@ }, "node_modules/events": { "version": "3.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -12028,12 +12104,10 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -12053,7 +12127,6 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -12063,7 +12136,6 @@ }, "node_modules/fast-uri": { "version": "3.0.1", - "dev": true, "license": "MIT" }, "node_modules/fastest-levenshtein": { @@ -12076,7 +12148,6 @@ }, "node_modules/fastq": { "version": "1.17.1", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -12173,7 +12244,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -12900,7 +12970,6 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -12911,7 +12980,6 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/global-dirs": { @@ -13040,7 +13108,6 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -13091,7 +13158,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13595,7 +13661,6 @@ }, "node_modules/ignore": { "version": "5.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -13880,7 +13945,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13929,7 +13993,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -14019,7 +14082,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -15978,7 +16040,6 @@ }, "node_modules/jest-worker": { "version": "27.5.1", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -16192,7 +16253,6 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -16638,7 +16698,6 @@ }, "node_modules/loader-runner": { "version": "4.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -17045,12 +17104,10 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -17066,7 +17123,6 @@ }, "node_modules/micromatch": { "version": "4.0.7", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -17089,7 +17145,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -17097,7 +17152,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -17399,7 +17453,6 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "dev": true, "license": "MIT" }, "node_modules/no-case": { @@ -17512,12 +17565,10 @@ }, "node_modules/node-releases": { "version": "2.0.14", - "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -18264,7 +18315,6 @@ }, "node_modules/picomatch": { "version": "2.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -18649,7 +18699,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -18696,7 +18745,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "dev": true, "funding": [ { "type": "github", @@ -18715,7 +18763,6 @@ }, "node_modules/randombytes": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -19161,7 +19208,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -19247,7 +19293,6 @@ }, "node_modules/reusify": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -19289,7 +19334,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "dev": true, "funding": [ { "type": "github", @@ -19342,7 +19386,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "dev": true, "funding": [ { "type": "github", @@ -19400,7 +19443,6 @@ }, "node_modules/schema-utils": { "version": "3.3.0", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -19483,7 +19525,6 @@ }, "node_modules/serialize-javascript": { "version": "6.0.2", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -19744,7 +19785,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -19760,7 +19800,6 @@ }, "node_modules/source-map-support": { "version": "0.5.21", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -20626,7 +20665,6 @@ }, "node_modules/supports-color": { "version": "8.1.1", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -20793,7 +20831,6 @@ }, "node_modules/tapable": { "version": "2.2.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -20953,7 +20990,6 @@ }, "node_modules/terser": { "version": "5.31.2", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -20970,7 +21006,6 @@ }, "node_modules/terser-webpack-plugin": { "version": "5.3.10", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", @@ -21125,7 +21160,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -21534,7 +21568,6 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -21575,7 +21608,6 @@ }, "node_modules/unicorn-magic": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -21678,7 +21710,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.0", - "dev": true, "funding": [ { "type": "opencollective", @@ -21707,7 +21738,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -21855,7 +21885,6 @@ }, "node_modules/watchpack": { "version": "2.4.1", - "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -21891,7 +21920,6 @@ }, "node_modules/webpack": { "version": "5.92.1", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -22277,7 +22305,6 @@ }, "node_modules/webpack-sources": { "version": "3.2.3", - "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" diff --git a/frontend/package.json b/frontend/package.json index ab62dc39a..97c34f4a1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "@sentry/react": "^8.24.0", "@tanstack/react-query": "^5.51.1", "@tanstack/react-query-devtools": "^5.51.23", + "copy-webpack-plugin": "^12.0.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-qr-code": "^2.0.15", diff --git a/frontend/public/assets/thumbnail.png b/frontend/public/assets/thumbnail.png new file mode 100644 index 000000000..3bd0839fe Binary files /dev/null and b/frontend/public/assets/thumbnail.png differ diff --git a/frontend/public/index.html b/frontend/public/index.html index 3f77054d4..0c823d1a2 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -7,6 +7,16 @@ name="description" content="심심풀이 땅콩처럼 가벼운 주제로 친구들과 즐기는 단체 대화주제 제공 서비스" /> + + + +