diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..a34b0285 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,118 @@ +name: EAT-SSU Server 개발 & 운영 서버 배포 파이프라인 + +on: + push: + branches: [ "main", "develop" ] + +permissions: + contents: read + +jobs: + CI-CD: + name: CI/CD + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: JDK 17 설치 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Gradle 캐싱 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: dev 프로필 설정 + if: contains(github.ref, 'dev') + run: | + echo "spring: + profiles: + include: dev" > ./src/main/resources/application.yml + shell: bash + + - name: prod 프로필 설정 + if: contains(github.ref, 'main') + run: | + echo "spring: + profiles: + include: prod" > ./src/main/resources/application.yml + shell: bash + + - name: gradlew 실행 권한 부여 + run: chmod +x gradlew + + - name: Gradle로 빌드 + run: ./gradlew clean build -x test + + - name: Docker Hub 로그인 + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: prod 용 Docker 빌드 및 푸시 + if: contains(github.ref, 'main') + run: | + docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/eatssu-prod . + docker push ${{ secrets.DOCKER_REPO }}/eatssu-prod + + - name: dev 서버 용 Docker 빌드 및 푸시 + if: contains(github.ref, 'dev') + run: | + docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/eatssu-dev . + docker push ${{ secrets.DOCKER_REPO }}/eatssu-dev + + - name: prod에 배포 + uses: appleboy/ssh-action@master + id: deploy-prod + if: contains(github.ref, 'main') + with: + host: ${{ secrets.HOST_PROD }} + username: ubuntu + key: ${{ secrets.PROD_PRIVATE_KEY }} + envs: GITHUB_SHA + script: | + sudo docker ps + sudo docker rm -f $(docker ps -qa) + sudo docker pull ${{ secrets.DOCKER_REPO }}/eatssu-prod + sudo docker run -d -p 9000:9000 \ + -e EATSSU_DB_URL_PROD="${{ secrets.EATSSU_DB_URL_PROD }}" \ + -e EATSSU_DB_USERNAME="${{ secrets.EATSSU_DB_USERNAME }}" \ + -e EATSSU_DB_PASSWORD="${{ secrets.EATSSU_DB_PASSWORD }}" \ + -e EATSSU_JWT_SECRET_PROD="${{ secrets.EATSSU_JWT_SECRET_PROD }}" \ + -e EATSSU_AWS_ACCESS_KEY_PROD="${{ secrets.EATSSU_AWS_ACCESS_KEY_PROD }}" \ + -e EATSSU_AWS_SECRET_KEY_PROD="${{ secrets.EATSSU_AWS_SECRET_KEY_PROD }}" \ + -e EATSSU_SLACK_TOKEN="${{ secrets.EATSSU_SLACK_TOKEN }}" \ + ${{ secrets.DOCKER_REPO }}/eatssu-prod + sudo docker image prune -f + + - name: dev 서버에 배포 + uses: appleboy/ssh-action@master + id: deploy-dev + if: contains(github.ref, 'dev') + with: + host: ${{ secrets.HOST_DEV }} + username: ${{ secrets.USERNAME }} + port: 22 + key: ${{ secrets.DEV_PRIVATE_KEY }} + script: | + sudo docker ps + sudo docker rm -f $(docker ps -qa) + sudo docker pull ${{ secrets.DOCKER_REPO }}/eatssu-dev + sudo docker run -d -p 9000:9000 \ + -e EATSSU_DB_URL_DEV="${{ secrets.EATSSU_DB_URL_DEV }}" \ + -e EATSSU_DB_USERNAME="${{ secrets.EATSSU_DB_USERNAME }}" \ + -e EATSSU_DB_PASSWORD="${{ secrets.EATSSU_DB_PASSWORD }}" \ + -e EATSSU_JWT_SECRET_DEV="${{ secrets.EATSSU_JWT_SECRET_DEV }}" \ + -e EATSSU_AWS_ACCESS_KEY_DEV="${{ secrets.EATSSU_AWS_ACCESS_KEY_DEV }}" \ + -e EATSSU_AWS_SECRET_KEY_DEV="${{ secrets.EATSSU_AWS_SECRET_KEY_DEV }}" \ + -e EATSSU_SLACK_TOKEN="${{ secrets.EATSSU_SLACK_TOKEN }}" \ + ${{ secrets.DOCKER_REPO }}/eatssu-dev + sudo docker image prune -f diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index ed350a06..00000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: CI/CD github Actions & Docker - -on: - push: - branches: [ "main", "dev" ] - -permissions: - contents: read - -jobs: - CI-CD: - runs-on: ubuntu-latest - steps: - # JDK setting - github actions에서 사용할 JDK 설정 (aws 과 project의 java 버전과 별도로 관리) - - uses: actions/checkout@v3 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - ## gradle caching (빌드 시간 줄이기) - - name: Gradle Caching - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - # 환경별 yml 파일 생성(1) - dev - - name: make dev server application.yml - if: contains(github.ref, 'dev') - run: | - cd ./src/main/resources - touch ./application.yml - echo "${{ secrets.YML_DEV }}" > ./application.yml - shell: bash - - # 환경별 yml 파일 생성(2) - prod - - name: make prod server application.yml - if: contains(github.ref, 'main') - run: | - cd ./src/main/resources - touch ./application.yml - echo "${{ secrets.YML_PROD }}" > ./application.yml - shell: bash - - # gradle chmod - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - # gradle build - - name: Build with Gradle - run: ./gradlew clean build -x test - - # docker login - - name: Docker Hub Login - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - # docker build & push to production - - name: Docker build & push to prod - if: contains(github.ref, 'main') - run: | - docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/eatssu-prod . - docker push ${{ secrets.DOCKER_REPO }}/eatssu-prod - - # docker build & push to develop - - name: Docker build & push to dev server - if: contains(github.ref, 'dev') - run: | - docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/eatssu-dev . - docker push ${{ secrets.DOCKER_REPO }}/eatssu-dev - - ## deploy to production - - name: Deploy to prod - uses: appleboy/ssh-action@master - id: deploy-prod - if: contains(github.ref, 'main') - with: - host: ${{ secrets.HOST_PROD }} # EC2 퍼블릭 IPv4 DNS - username: ubuntu - key: ${{ secrets.PROD_PRIVATE_KEY }} - envs: GITHUB_SHA - script: | - sudo docker ps - sudo docker rm -f $(docker ps -qa) - sudo docker pull ${{ secrets.DOCKER_REPO }}/eatssu-prod - sudo docker run -d -p 9000:9000 ${{ secrets.DOCKER_REPO }}/eatssu-prod - sudo docker image prune -f - - ## deploy to develop - - name: Deploy to dev server - uses: appleboy/ssh-action@master - id: deploy-dev - if: contains(github.ref, 'dev') - with: - host: ${{ secrets.HOST_DEV }} # EC2 퍼블릭 IPv4 DNS - username: ${{ secrets.USERNAME }} # ubuntu - port: 22 - key: ${{ secrets.DEV_PRIVATE_KEY }} - script: | - sudo docker ps - sudo docker rm -f $(docker ps -qa) - sudo docker pull ${{ secrets.DOCKER_REPO }}/eatssu-dev - sudo docker run -d -p 9000:9000 ${{ secrets.DOCKER_REPO }}/eatssu-dev - sudo docker image prune -f diff --git a/.gitignore b/.gitignore index 1399cc26..4ec9ead9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,14 +36,6 @@ out/ ### VS Code ### .vscode/ -### application files ### -application.yml -application-local.yml -application-dev.yml -application-test.yml -application-prod.yml - - ### Generated files ### /src/main/generated/ @@ -51,4 +43,13 @@ application-prod.yml /eat-ssu.tar ### SDKMAN ### -.sdkmanrc \ No newline at end of file +.sdkmanrc + +### Junie guideline ### +.junie + +### Cursor guideline ### +eatssu-server-rule.mdc + +### Copilot guildline ### +copilot-instructions.md \ No newline at end of file diff --git a/.junie/guidelines.md b/.junie/guidelines.md deleted file mode 100644 index efe7ac65..00000000 --- a/.junie/guidelines.md +++ /dev/null @@ -1,130 +0,0 @@ -# EAT-SSU 프로젝트 가이드라인 - -## 프로젝트 개요 - -<<<<<<< HEAD -EAT-SSU는 숭실대학교 학생들이 캠퍼스 내 식당 메뉴를 리뷰하고 비교할 수 있는 모바일 애플리케이션입니다. 이 앱은 메뉴 리뷰, 운영 시간, 위치 등 다양한 식사 옵션에 대한 종합적인 정보를 제공하여 식사 경험을 향상시키는 것을 목표로 합니다. -======= -EAT-SSU는 숭실대학교 학생들이 캠퍼스 내 식당 메뉴를 리뷰하고 비교할 수 있는 모바일 애플리케이션입니다. 이 앱은 메뉴 리뷰, 운영 시간, 위치 등 다양한 식사 옵션에 대한 종합적인 정보를 제공하여 식사 경험을 -향상시키는 것을 목표로 합니다. ->>>>>>> bbb0601 (docs: Java SDK 17 명세) - -### 주요 기능 - -- 캠퍼스 내 모든 식당 시설의 메뉴 보기 및 비교: -<<<<<<< HEAD - - 학생식당 - - 도담식당 - - 푸드코트 - - 스낵코너 - - 기숙사 식당 -======= - - 학생식당 - - 도담식당 - - 푸드코트 - - 스낵코너 - - 기숙사 식당 ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- 식사에 대한 리뷰 작성 및 읽기 -- 식당 운영 시간 및 위치 확인 -- 사용자 인증 시스템 - -## 기술 스택 - -### 백엔드 -<<<<<<< HEAD -======= - ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- **프레임워크**: Spring Boot 3.0.4 -- **언어**: Java 17 -- **데이터베이스**: MySQL (프로덕션), H2 (테스팅) -- **ORM**: Spring Data JPA와 QueryDSL -- **보안**: Spring Security와 JWT -- **API 문서화**: OpenAPI (Springdoc) -- **빌드 도구**: Gradle -- **클라우드 스토리지**: AWS S3 -- **알림**: Slack API 통합 - -### 모바일 애플리케이션 -<<<<<<< HEAD -======= - ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- iOS(App Store)와 Android(Play Store) 모두에서 이용 가능 - -## 프로젝트 구조 - -이 프로젝트는 다음과 같은 구조로 도메인 주도 설계 접근 방식을 따릅니다: - -``` -src/main/java/ssu/eatssu/ -├── domain/ -│ ├── menu/ -│ │ ├── entity/ -│ │ ├── persistence/ -│ │ ├── presentation/ -│ │ │ ├── dto/ -│ │ │ ├── rest/ -│ │ └── service/ -│ ├── restaurant/ -│ │ ├── entity/ -│ │ ├── persistence/ -│ │ ├── presentation/ -│ │ └── service/ -│ └── [기타 도메인] -├── global/ -│ ├── config/ -│ ├── error/ -│ └── security/ -└── [기타 패키지] -``` - -## 개발 가이드라인 - -### 코드 스타일 -<<<<<<< HEAD -======= - ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- Java 코딩 규칙 준수 -- 의미 있는 변수 및 메소드 이름 사용 -- 복잡한 로직에 대한 포괄적인 주석 작성 -- 모든 새로운 기능에 대한 단위 테스트 작성 - -### Git 워크플로우 -<<<<<<< HEAD -======= - ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- 메인 브랜치에서 기능 브랜치 생성 -- 설명적인 커밋 메시지 사용 -- 병합 전 코드 리뷰를 위한 풀 리퀘스트 제출 -- 커밋을 집중적이고 원자적으로 유지 - -### 보안 -<<<<<<< HEAD -======= - ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- 민감한 정보(API 키, 자격 증명)를 절대 커밋하지 않음 -- `application.yml` 파일은 팀 내에서만 공유 -- 항상 사용자 입력 검증 -- 최소 권한 원칙 준수 - -### 테스팅 -<<<<<<< HEAD -======= - ->>>>>>> bbb0601 (docs: Java SDK 17 명세) -- 모든 서비스에 대한 단위 테스트 작성 -- API 엔드포인트에 대한 통합 테스트 사용 -- 중요 구성 요소에 대한 테스트 커버리지 유지 - -## 배포 - -애플리케이션은 Docker 컨테이너로 배포됩니다. 빌드 프로세스는 `eat-ssu.jar`라는 JAR 파일을 생성합니다. - -## 연락처 - -프로젝트에 대한 자세한 정보는 [프로젝트 페이지](https://hi-jin-1514.notion.site/EAT-SSU-b04aaec9b7814a628c6ef6b3e08c74a3)를 방문하세요. \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java index 8827919a..3804b923 100644 --- a/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java +++ b/src/main/java/ssu/eatssu/domain/auth/infrastructure/SecurityConfig.java @@ -23,14 +23,14 @@ @RequiredArgsConstructor public class SecurityConfig { private static final String[] RESOURCE_LIST = { - "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**","/oauths/valid/token", "/admin/img/**", "/css/**", "/js/**", + "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**","/oauths/valid/token", "/admin/img/**", "/css/**", "/js/**", "/favicon.ico", "/error/**", "/webjars/**", "/h2-console/**" }; private static final String[] AUTH_WHITELIST = { "/", "/oauths/kakao", "/oauths/apple", "/menus/**", "/meals/**", "/admin/login", "/reviews", "/reviews/menus/**", "/reviews/meals/**", "/v2/reviews/statistics", "/v2/reviews", - "/partnerships/**","/v2/reviews/menus/**","/v2/reviews/meals/**","/actuator/**" + "/partnerships/**","/v2/reviews/menus/**","/v2/reviews/meals/**","/actuator/**","/error-test/**" }; private static final String[] ADMIN_PAGE_LIST = { diff --git a/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java b/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java index 7462e912..d02ed5be 100644 --- a/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java +++ b/src/main/java/ssu/eatssu/domain/menu/entity/Menu.java @@ -97,19 +97,32 @@ public boolean isContinued() { return !this.isDiscontinued; } - public void increaseLikeCount() { - likeCount++; + public void increaseLikeCount() + { + if(this.likeCount==null){ + this.likeCount=0; + } + this.likeCount++; } public void increaseUnlikeCount() { - unlikeCount++; + if(this.unlikeCount==null){ + this.unlikeCount=0; + } + this.unlikeCount++; } public void decreaseLikeCount() { - likeCount--; + if(this.likeCount==null){ + this.likeCount=0; + } + this.likeCount--; } public void decreaseUnlikeCount() { + if(this.unlikeCount==null){ + this.unlikeCount=0; + } unlikeCount--; } diff --git a/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java b/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java index 21c93a8a..dc2999b2 100644 --- a/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java +++ b/src/main/java/ssu/eatssu/domain/menu/persistence/MealMenuRepository.java @@ -19,4 +19,7 @@ public interface MealMenuRepository extends JpaRepository { @Query("SELECT DISTINCT mm.meal.id FROM MealMenu mm WHERE mm.menu.id IN :menuIds") List findMealIdsByMenuIds(@Param("menuIds") List menuIds); + + @Query("SELECT mm.menu FROM MealMenu mm WHERE mm.meal = :meal") + List findMenusByMeal(Meal meal); } diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java new file mode 100644 index 00000000..ff19e98c --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/dto/MealReviewsV2Response.java @@ -0,0 +1,44 @@ +package ssu.eatssu.domain.review.dto; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import ssu.eatssu.domain.menu.entity.Meal; + +@Schema(title = "식단 리뷰 정보V2(평점 등등)") +@Getter +@AllArgsConstructor +@Builder +public class MealReviewsV2Response implements ReviewInformationResponse{ + @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") + private List menuNames; + + @Schema(description = "리뷰 개수", example = "15") + private Long totalReviewCount; + + @Schema(description = "평점-메인", example = "4.4") + private Double mainRating; + + @Schema(description = "좋아요 개수", example = "4.4") + private Integer likeCount; + + @Schema(description = "싫어요 개수", example = "4.4") + private Integer unlikeCount; + + @Schema(description = "평점 별 갯수") + private ReviewRatingCount reviewRatingCount; + + public static MealReviewsV2Response of(Long totalReviewCount, List menuNames, + RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount) { + + return MealReviewsV2Response.builder() + .menuNames(menuNames) + .mainRating(ratingAverages.mainRating()) + .totalReviewCount(totalReviewCount) + .reviewRatingCount(reviewRatingCount) + .build(); + } +} diff --git a/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java new file mode 100644 index 00000000..15c753a8 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/dto/MenuReviewsV2Response.java @@ -0,0 +1,47 @@ +package ssu.eatssu.domain.review.dto; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import ssu.eatssu.domain.menu.entity.Meal; +import ssu.eatssu.domain.menu.entity.Menu; + +@Schema(title = "메뉴 리뷰 정보V2(평점 등등)") +@Getter +@AllArgsConstructor +@Builder +public class MenuReviewsV2Response implements ReviewInformationResponse { + @Schema(description = "메뉴명 리스트", example = "['고구마치즈돈까스', '막국수', '미니밥','단무지', '요구르트']") + private List menuNames; + + @Schema(description = "리뷰 개수", example = "15") + private Long totalReviewCount; + + @Schema(description = "평점-메인", example = "4.4") + private Double mainRating; + + @Schema(description = "좋아요 개수", example = "4.4") + private Integer likeCount; + + @Schema(description = "싫어요 개수", example = "4.4") + private Integer unlikeCount; + + @Schema(description = "평점 별 갯수") + private ReviewRatingCount reviewRatingCount; + + public static MenuReviewsV2Response of(Long totalReviewCount, List menuNames, + RatingAverages ratingAverages, ReviewRatingCount reviewRatingCount,Menu menu) { + + return MenuReviewsV2Response.builder() + .menuNames(menuNames) + .mainRating(ratingAverages.mainRating()) + .likeCount(menu.getLikeCount()) + .unlikeCount(menu.getUnlikeCount()) + .totalReviewCount(totalReviewCount) + .reviewRatingCount(reviewRatingCount) + .build(); + } +} diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/MealReviewController.java b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java similarity index 86% rename from src/main/java/ssu/eatssu/domain/review/presentation/MealReviewController.java rename to src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java index 0305657f..da44bb5f 100644 --- a/src/main/java/ssu/eatssu/domain/review/presentation/MealReviewController.java +++ b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java @@ -27,11 +27,12 @@ import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.domain.review.dto.CreateMealReviewRequest; import ssu.eatssu.domain.review.dto.MealReviewResponse; -import ssu.eatssu.domain.review.dto.MealReviewsResponse; +import ssu.eatssu.domain.review.dto.MealReviewsV2Response; import ssu.eatssu.domain.review.dto.MenuReviewResponse; +import ssu.eatssu.domain.review.dto.MenuReviewsV2Response; import ssu.eatssu.domain.review.dto.RestaurantReviewResponse; import ssu.eatssu.domain.review.dto.UpdateMealReviewRequest; -import ssu.eatssu.domain.review.service.MealReviewService; +import ssu.eatssu.domain.review.service.ReviewServiceV2; import ssu.eatssu.domain.review.service.ReviewService; import ssu.eatssu.domain.slice.dto.SliceResponse; import ssu.eatssu.global.handler.response.BaseResponse; @@ -40,8 +41,8 @@ @RequiredArgsConstructor @RequestMapping("/v2/reviews") @Tag(name = "Review V2", description = "리뷰 V2 API") -public class MealReviewController { - private final MealReviewService mealReviewService; +public class ReviewControllerV2 { + private final ReviewServiceV2 reviewServiceV2; private final ReviewService reviewService; @@ -56,7 +57,7 @@ public class MealReviewController { public BaseResponse createReview( @RequestBody CreateMealReviewRequest createMealReviewRequest, @AuthenticationPrincipal CustomUserDetails customUserDetails) { - mealReviewService.createReview(customUserDetails, createMealReviewRequest); + reviewServiceV2.createReview(customUserDetails, createMealReviewRequest); return BaseResponse.success(); } @@ -74,7 +75,7 @@ public BaseResponse getRestaurantReviews( @Parameter(description = "restaurant") @RequestParam Restaurant restaurant ) { - return BaseResponse.success(mealReviewService.findRestaurantReviews(restaurant)); + return BaseResponse.success(reviewServiceV2.findRestaurantReviews(restaurant)); } @Operation(summary = "리뷰 리스트 조회", description = """ @@ -94,7 +95,7 @@ public BaseResponse> getReviews( @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, @AuthenticationPrincipal CustomUserDetails customUserDetails) { Pageable pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "id")); - SliceResponse myReviews = mealReviewService.findReviews(mealId, lastReviewId, pageable, + SliceResponse myReviews = reviewServiceV2.findReviews(mealId, lastReviewId, pageable, customUserDetails); return BaseResponse.success(myReviews); } @@ -117,7 +118,7 @@ public BaseResponse updateReview(@Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, @RequestBody UpdateMealReviewRequest request, @AuthenticationPrincipal CustomUserDetails customUserDetails) { - mealReviewService.updateReview(customUserDetails, reviewId, request); + reviewServiceV2.updateReview(customUserDetails, reviewId, request); return BaseResponse.success(); } @@ -132,7 +133,7 @@ public BaseResponse updateReview(@Parameter(description = "reviewId") public BaseResponse deleteReview( @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, @AuthenticationPrincipal CustomUserDetails customUserDetails) { - mealReviewService.deleteReview(customUserDetails, reviewId); + reviewServiceV2.deleteReview(customUserDetails, reviewId); return BaseResponse.success(); } @@ -146,13 +147,13 @@ public BaseResponse deleteReview( public BaseResponse toggleReviewLike( @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, @AuthenticationPrincipal CustomUserDetails customUserDetails) { - mealReviewService.toggleReviewLike(customUserDetails, reviewId); + reviewServiceV2.toggleReviewLike(customUserDetails, reviewId); return BaseResponse.success(); } - @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ 식단 리뷰 정보를 조회하는 API 입니다.

- 메뉴명 리스트, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. + 메뉴명 리스트, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. """) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), @@ -162,15 +163,15 @@ public BaseResponse toggleReviewLike( @Schema(implementation = BaseResponse.class))) }) @GetMapping("/meals/{mealId}") - public BaseResponse getMealReviews( + public BaseResponse getMealReviews( @Parameter(description = "mealId") @PathVariable(value = "mealId") Long mealId) { - return BaseResponse.success(reviewService.findMealReviews(mealId)); + return BaseResponse.success(reviewServiceV2.findMealReviews(mealId)); } - @Operation(summary = "고정 메뉴 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + @Operation(summary = "고정 메뉴 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

- 메뉴명, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. + 메뉴명, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. """) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), @@ -180,10 +181,10 @@ public BaseResponse getMealReviews( @Schema(implementation = BaseResponse.class))) }) @GetMapping("/menus/{menuId}") - public BaseResponse getMainReviews( + public BaseResponse getMainReviews( @Parameter(description = "menuId") @PathVariable(value = "menuId") Long menuId) { - return BaseResponse.success(reviewService.findMenuReviews(menuId)); + return BaseResponse.success(reviewServiceV2.findMenuReviews(menuId)); } } diff --git a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java index fe3473ed..21aceef7 100644 --- a/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java +++ b/src/main/java/ssu/eatssu/domain/review/repository/ReviewRepository.java @@ -17,6 +17,8 @@ public interface ReviewRepository extends JpaRepository, ReviewRep List findAllByMenu(Menu menu); + List findAllByMeal(Meal meal); + /** * dto projection */ diff --git a/src/main/java/ssu/eatssu/domain/review/service/MealReviewService.java b/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java similarity index 70% rename from src/main/java/ssu/eatssu/domain/review/service/MealReviewService.java rename to src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java index bbf1584b..36ac0359 100644 --- a/src/main/java/ssu/eatssu/domain/review/service/MealReviewService.java +++ b/src/main/java/ssu/eatssu/domain/review/service/ReviewServiceV2.java @@ -2,8 +2,10 @@ import static ssu.eatssu.global.handler.response.BaseResponseStatus.*; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -23,7 +25,9 @@ import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.domain.review.dto.CreateMealReviewRequest; import ssu.eatssu.domain.review.dto.MealReviewResponse; +import ssu.eatssu.domain.review.dto.MealReviewsV2Response; import ssu.eatssu.domain.review.dto.MenuLikeRequest; +import ssu.eatssu.domain.review.dto.MenuReviewsV2Response; import ssu.eatssu.domain.review.dto.RestaurantReviewResponse; import ssu.eatssu.domain.review.dto.ReviewRatingCount; import ssu.eatssu.domain.review.dto.UpdateMealReviewRequest; @@ -39,7 +43,7 @@ @RequiredArgsConstructor @Service -public class MealReviewService { +public class ReviewServiceV2 { private final UserRepository userRepository; private final ReviewRepository reviewRepository; private final MenuRepository menuRepository; @@ -79,18 +83,34 @@ public RestaurantReviewResponse findRestaurantReviews(Restaurant restaurant) { List reviews = reviewRepository.findByMealIn(meals); List menus = mealMenuRepository.findMenusByMeals(meals); - Double averageRating = reviews.stream() - .mapToInt(Review::getRating) - .average() - .orElse(0.0); - - Integer likeCount = menus.stream() - .mapToInt(Menu::getLikeCount) - .sum(); - - Integer unlikeCount = menus.stream() - .mapToInt(Menu::getUnlikeCount) - .sum(); + Double averageRating = Optional.ofNullable(reviews) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + + Integer likeCount = Optional.ofNullable(menus) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Menu::getLikeCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum(); + + + Integer unlikeCount = Optional.ofNullable(menus) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Menu::getUnlikeCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum(); ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); @@ -136,6 +156,87 @@ public SliceResponse findReviews(Long mealId, Long lastRevie .build(); } + /** + * 특정 Menu 리뷰 조회 + */ + public MenuReviewsV2Response findMenuReviews(Long menuId) { + Menu menu = menuRepository.findById(menuId).orElseThrow(()-> new BaseException(NOT_FOUND_MENU)); + List reviews = reviewRepository.findAllByMenu(menu); + + Double averageRating = Optional.ofNullable(reviews) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + + Integer likeCount = menu.getLikeCount(); + + + Integer unlikeCount = menu.getUnlikeCount(); + + ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); + + return MenuReviewsV2Response.builder() + .totalReviewCount((long)reviews.size()) + .reviewRatingCount(reviewRatingCount) + .mainRating(Math.round(averageRating * 10) / 10.0) + .likeCount(likeCount) + .unlikeCount(unlikeCount) + .build(); + } + + /** + * 특정 Meal 리뷰 조회 + */ + public MealReviewsV2Response findMealReviews(Long mealId) { + Meal meal = mealRepository.findById(mealId).orElseThrow(()-> new BaseException(NOT_FOUND_MEAL)); + List reviews = reviewRepository.findAllByMeal(meal); + List menus = mealMenuRepository.findMenusByMeal(meal); + + Double averageRating = Optional.ofNullable(reviews) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + + Integer likeCount = Optional.ofNullable(menus) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Menu::getLikeCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum(); + + + Integer unlikeCount = Optional.ofNullable(menus) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .map(Menu::getUnlikeCount) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum(); + + ReviewRatingCount reviewRatingCount = ReviewRatingCount.from(reviews); + + return MealReviewsV2Response.builder() + .totalReviewCount((long)reviews.size()) + .reviewRatingCount(reviewRatingCount) + .mainRating(Math.round(averageRating * 10) / 10.0) + .likeCount(likeCount) + .unlikeCount(unlikeCount) + .build(); + } + /** * 리뷰 수정 */ diff --git a/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java b/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java index 7f327996..ce6f562a 100644 --- a/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java +++ b/src/main/java/ssu/eatssu/domain/slack/entity/SlackChannel.java @@ -7,7 +7,8 @@ public enum SlackChannel { REPORT_CHANNEL("#신고"), ADDMENU_CHANNEL("#메뉴_추가"), ERROR_CHANNEL("#장애"), - USER_INQUIRY_CHANNEL("#유저-문의"); + USER_INQUIRY_CHANNEL("#유저-문의"), + SERVER_ERROR("C092J4J6F0U"); @Getter private String krName; diff --git a/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java b/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java index 05616c0b..81cabe99 100644 --- a/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java +++ b/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java @@ -2,6 +2,7 @@ import java.text.MessageFormat; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import ssu.eatssu.domain.inquiry.entity.Inquiry; @@ -12,7 +13,10 @@ @Component public class SlackMessageFormat { - private SlackMessageFormat() { + private static String serverEnv; + + private SlackMessageFormat(@Value("${server.env:unknown}") String serverEnvValue) { + SlackMessageFormat.serverEnv = serverEnvValue; } public static String sendReport(Report report) { @@ -63,4 +67,24 @@ public static String sendUserInquiry(Inquiry inquiry) { , inquiry.getCreatedDate(), inquiry.getContent()}; return messageFormat.format(args); } + + + public static String sendServerError(Exception ex) { + MessageFormat messageFormat = new MessageFormat( + """ + =================== + *서버 에러 발생* + - 예외 클래스: {0} + - 예외 메시지: {1} + - 개발환경: {2} + =================== + """ + ); + Object[] args = { + ex.getClass().getName(), + ex.getMessage(), + serverEnv, + }; + return messageFormat.format(args); + } } diff --git a/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java b/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java index e3ed2b86..33a5a5d4 100644 --- a/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java +++ b/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java @@ -9,6 +9,7 @@ import com.slack.api.methods.MethodsClient; import com.slack.api.methods.SlackApiException; import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import com.slack.api.methods.response.chat.ChatPostMessageResponse; import lombok.extern.slf4j.Slf4j; import ssu.eatssu.domain.slack.entity.SlackChannel; diff --git a/src/main/java/ssu/eatssu/domain/user/config/UserProperties.java b/src/main/java/ssu/eatssu/domain/user/config/UserProperties.java new file mode 100644 index 00000000..182ef04e --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/user/config/UserProperties.java @@ -0,0 +1,19 @@ +package ssu.eatssu.domain.user.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "user") +public class UserProperties { + + private List forbiddenNicknames = new ArrayList<>(); + +} \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java b/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java index d0726302..77d58615 100644 --- a/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java +++ b/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java @@ -30,7 +30,7 @@ import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.partnership.dto.PartnershipResponse; import ssu.eatssu.domain.partnership.service.PartnershipService; -import ssu.eatssu.domain.review.service.MealReviewService; +import ssu.eatssu.domain.review.service.ReviewServiceV2; import ssu.eatssu.domain.slice.dto.SliceResponse; import ssu.eatssu.domain.slice.service.SliceService; import ssu.eatssu.domain.user.dto.DepartmentResponse; @@ -50,7 +50,7 @@ public class UserController { private final UserService userService; private final SliceService sliceService; - private final MealReviewService mealReviewService; + private final ReviewServiceV2 mealReviewService; private final PartnershipService partnershipService; @Operation(summary = "이메일 중복 체크", description = """ diff --git a/src/main/java/ssu/eatssu/domain/user/service/UserService.java b/src/main/java/ssu/eatssu/domain/user/service/UserService.java index 8b96eede..cd9c4dd4 100644 --- a/src/main/java/ssu/eatssu/domain/user/service/UserService.java +++ b/src/main/java/ssu/eatssu/domain/user/service/UserService.java @@ -14,6 +14,7 @@ import ssu.eatssu.domain.auth.entity.OAuthProvider; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.review.entity.Review; +import ssu.eatssu.domain.user.config.UserProperties; import ssu.eatssu.domain.user.department.entity.Department; import ssu.eatssu.domain.user.department.persistence.DepartmentRepository; import ssu.eatssu.domain.user.dto.DepartmentResponse; @@ -34,6 +35,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final DepartmentRepository departmentRepository; + private final UserProperties userProperties; public User join(String email, OAuthProvider provider, String providerId) { String credentials = createCredentials(provider, providerId); @@ -46,6 +48,10 @@ public void updateNickname(CustomUserDetails userDetails, NicknameUpdateRequest User user = userRepository.findById(userDetails.getId()) .orElseThrow(() -> new BaseException(NOT_FOUND_USER)); + if (isForbiddenNickname(request.nickname()) || userRepository.existsByNickname(request.nickname())) { + throw new BaseException(DUPLICATE_NICKNAME); + } + user.updateNickname(request.nickname()); } @@ -71,6 +77,9 @@ public Boolean validateDuplicatedEmail(String email) { } public Boolean validateDuplicatedNickname(String nickname) { + if (isForbiddenNickname(nickname)) { + return false; + } return !userRepository.existsByNickname(nickname); } @@ -109,4 +118,9 @@ public DepartmentResponse getDepartment(CustomUserDetails userDetails) { } return new DepartmentResponse(department.getName()); } + + private boolean isForbiddenNickname(String nickname) { + return userProperties.getForbiddenNicknames().stream() + .anyMatch(forbidden -> forbidden.equalsIgnoreCase(nickname)); + } } \ No newline at end of file diff --git a/src/main/java/ssu/eatssu/domain/user/util/UserAliasUtil.java b/src/main/java/ssu/eatssu/domain/user/util/UserAliasUtil.java index 56dbfecb..b02c9efa 100644 --- a/src/main/java/ssu/eatssu/domain/user/util/UserAliasUtil.java +++ b/src/main/java/ssu/eatssu/domain/user/util/UserAliasUtil.java @@ -1,6 +1,7 @@ package ssu.eatssu.domain.user.util; import java.util.List; +import java.util.Objects; import ssu.eatssu.domain.review.entity.Review; import ssu.eatssu.domain.user.entity.User; @@ -11,9 +12,12 @@ public static String getUserAlias(User user, List reviews) { return "미슈테리 미식가"; } double avg = reviews.stream() - .mapToInt(Review::getRating) - .average() - .orElse(0.0); + .map(Review::getRating) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + int avgRating = (int)Math.round(avg); switch (avgRating) { case 1: diff --git a/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java b/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java new file mode 100644 index 00000000..e4cfd34d --- /dev/null +++ b/src/main/java/ssu/eatssu/global/handler/ErrorTestController.java @@ -0,0 +1,12 @@ +package ssu.eatssu.global.handler; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ErrorTestController { + @GetMapping("/error-test") + public String triggerError() { + throw new RuntimeException("임의로 발생시킨 런타임 에러입니다!"); + } +} diff --git a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java index 6a0df601..0e7b2c71 100644 --- a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java @@ -23,7 +23,10 @@ import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import ssu.eatssu.domain.slack.entity.SlackMessageFormat; +import ssu.eatssu.domain.slack.service.SlackService; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponse; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -33,7 +36,9 @@ */ @Slf4j @RestControllerAdvice +@RequiredArgsConstructor public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + private final SlackService slackService; /** * BaseException 처리 @@ -41,8 +46,16 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(BaseException.class) public ResponseEntity> handleBaseException(BaseException e) { log.info(e.getStatus().toString()); + sendErrorToSlack(e); return ResponseEntity.status(e.getStatus().getHttpStatus()).body(BaseResponse.fail(e.getStatus())); } + @ExceptionHandler(Exception.class) + public ResponseEntity> handleAllUnhandledException(Exception ex) { + sendErrorToSlack(ex); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_ERROR)); + } + /** * 경로는 있으나 지원하지 않는 http method로 요청 시 @@ -215,4 +228,35 @@ protected ResponseEntity handleHttpMessageNotWritable( .body(BaseResponse.fail(BaseResponseStatus.INTERNAL_SERVER_ERROR)); } + @Override + protected ResponseEntity handleExceptionInternal( + @NonNull Exception ex, + Object body, + @NonNull HttpHeaders headers, + @NonNull HttpStatusCode statusCode, + @NonNull WebRequest request) { + + HttpStatus status = HttpStatus.valueOf(statusCode.value()); + + if (status.is4xxClientError() || status.is5xxServerError()) { + sendErrorToSlack(ex); + } + + BaseResponseStatus responseStatus = status.is4xxClientError() + ? BaseResponseStatus.BAD_REQUEST + : BaseResponseStatus.INTERNAL_SERVER_ERROR; + + return ResponseEntity.status(status).body(BaseResponse.fail(responseStatus)); + } + + private void sendErrorToSlack(Exception ex) { + try { + String message = SlackMessageFormat.sendServerError(ex); + slackService.sendSlackMessage(message, ssu.eatssu.domain.slack.entity.SlackChannel.SERVER_ERROR); + } catch (Exception slackEx) { + log.warn("슬랙 전송 실패: {}", slackEx.getMessage()); + } + } + + } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 00000000..73e0008b --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,99 @@ +## port number +server: + port: 9000 + env: dev + + +spring: + ## Database + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${EATSSU_DB_URL_DEV} + username: ${EATSSU_DB_USERNAME} + password: ${EATSSU_DB_PASSWORD} + hikari: + maximum-pool-size: 200 + minimum-idle: 10 + connection-timeout: 2500 + connection-init-sql: SELECT 1 + validation-timeout: 2000 + idle-timeout: 600000 + max-lifetime: 1800000 + + ## JPA + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: false + show_sql: true + + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + +## Auth +jwt: + secret: + key: ${EATSSU_JWT_SECRET_DEV} + token-validity-in-seconds: 86400 + refresh-token-validity-in-seconds: 604800 + +#S3 +cloud: + aws: + credentials: + accessKey: ${EATSSU_AWS_ACCESS_KEY_DEV} + secretKey: ${EATSSU_AWS_SECRET_KEY_DEV} + s3: + bucket: eatssu-bucket + region: + static: ap-northeast-2 + stack: + auto: false + +#Slack +slack: + token: ${EATSSU_SLACK_TOKEN} + +#Swagger +swagger: + url: https://dev.eat-ssu.store + description: Test Server Swagger API + +springdoc: + swagger-ui: + path: /swagger-ui.html + groups-order: DESC + operationsSorter: method + disable-swagger-default-url: true + display-request-duration: true + api-docs: + path: /v3/api-docs + show-actuator: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + paths-to-match: + - /** + +logging: + level: + root: INFO + com.zaxxer.hikari: INFO + +management: + endpoint: + metrics: + enabled: true + prometheus: + enabled: true + + endpoints: + web: + exposure: + include: health, info, metrics, prometheus diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 00000000..1a4ffcd6 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,96 @@ +## port number +server: + port: 9000 + env: local + + +spring: + ## Database + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${EATSSU_DB_URL_DEV} + username: ${EATSSU_DB_USERNAME} + password: ${EATSSU_DB_PASSWORD} + + ## JPA + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: true + show_sql: false + + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + +## Auth +jwt: + secret: + key: ${EATSSU_JWT_SECRET_LOCAL} + token-validity-in-seconds: 86400 + refresh-token-validity-in-seconds: 259200 + +#S3 +cloud: + aws: + credentials: + accessKey: ${EATSSU_AWS_ACCESS_KEY_DEV} + secretKey: ${EATSSU_AWS_SECRET_KEY_DEV} + s3: + bucket: eatssu-bucket + region: + static: ap-northeast-2 + stack: + auto: false + +#Slack +slack: + token: ${EATSSU_SLACK_TOKEN} + +#Swagger +swagger: + url: http://localhost:9000 + description: Test Server Swagger API + +springdoc: + swagger-ui: + # Swagger UI + path: /swagger-ui.html + # Group + groups-order: DESC + # API + operationsSorter: method + # Swagger UI + disable-swagger-default-url: true + # API + display-request-duration: true + api-docs: + path: /v3/api-docs + show-actuator: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + paths-to-match: + - /** + +logging: + level: + root: INFO + com.zaxxer.hikari: INFO + +management: + endpoint: + metrics: + enabled: true + prometheus: + enabled: true + + endpoints: + web: + exposure: + include: health, info, metrics, prometheus diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 00000000..29d82927 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,106 @@ +## port number +server: + port: 9000 + env: prod + + +spring: + ## Database + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${EATSSU_DB_URL_PROD} + username: ${EATSSU_DB_USERNAME} + password: ${EATSSU_DB_PASSWORD} + hikari: + maximum-pool-size: 200 + minimum-idle: 20 + connection-timeout: 2500 + connection-init-sql: SELECT 1 + validation-timeout: 2000 + idle-timeout: 600000 + max-lifetime: 1500000 + + ## JPA + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + format_sql: true + show_sql: false + + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + +## Auth +jwt: + secret: + key: ${EATSSU_JWT_SECRET_PROD} + token-validity-in-seconds: 86400 + refresh-token-validity-in-seconds: 604800 + +#S3 +cloud: + aws: + credentials: + accessKey: ${EATSSU_AWS_ACCESS_KEY_PROD} + secretKey: ${EATSSU_AWS_SECRET_KEY_PROD} + s3: + bucket: eatssu-prod-bucket + region: + static: ap-northeast-2 + stack: + auto: false + +#Slack +slack: + token: ${EATSSU_SLACK_TOKEN} + +#Swagger +swagger: + url: https://eat-ssu.store + description: Prod-Server url + +springdoc: + swagger-ui: + # Swagger UI + path: /swagger-ui.html + # Group + groups-order: DESC + # API + operationsSorter: method + # Swagger UI + disable-swagger-default-url: true + # API + display-request-duration: true + api-docs: + path: /v3/api-docs + show-actuator: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + paths-to-match: + - /** + +logging: + level: + root: INFO + com.zaxxer.hikari: INFO + + +management: + endpoint: + metrics: + enabled: true + prometheus: + enabled: true + + endpoints: + web: + exposure: + include: health, info, metrics, prometheus + diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 00000000..e6835a44 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,74 @@ +## port number +server: + port: 9000 + + +spring: + ## Database + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${EATSSU_DB_URL_TEST} + username: ${EATSSU_DB_USERNAME_TEST} + password: ${EATSSU_DB_PASSWORD_TEST} + + ## JPA + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: +# jdbc: + lob: + non_contextual_creation: true + format_sql: true + show_sql: true + dialect: org.hibernate.dialect.MySQLDialect + + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + +## Auth +jwt: + secret: + key: ${EATSSU_JWT_SECRET_TEST} + token-validity-in-seconds: 86400 + refresh-token-validity-in-seconds: 259200 + +#S3 +cloud: + aws: + credentials: + accessKey: ${EATSSU_AWS_ACCESS_KEY_PROD} + secretKey: ${EATSSU_AWS_SECRET_KEY_PROD} + s3: + bucket: eatssu-dev-bucket + region: + static: ap-northeast-2 + stack: + auto: false + +#Slack +slack: + token: ${EATSSU_SLACK_TOKEN} + +#Swagger +swagger: + url: http://localhost:9000 + description: Test Server Swagger API + +springdoc: + swagger-ui: + path: /swagger-ui.html + groups-order: DESC + operationsSorter: method + disable-swagger-default-url: true + display-request-duration: true + api-docs: + path: /v3/api-docs + show-actuator: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + paths-to-match: + - /** \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..d2133024 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,11 @@ +spring: + profiles: + # include : prod + # include : dev + include : local + # include: test + +user: + forbidden-nicknames: + - EAT-SSU + - EATSSU