diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 1644596c..f9172738 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -59,10 +59,7 @@ jobs:
runs-on: self-hosted
# weekly/* 브랜치에서는 실행되지 않도록 조건 추가
- # if: startsWith(github.ref, 'refs/heads/Master') || startsWith(github.ref, 'refs/heads/develop')
-
- # weekly/* 브랜치에서만 실행되도록
- if: startsWith(github.ref, 'refs/heads/weekly/')
+ if: startsWith(github.ref, 'refs/heads/Master') || startsWith(github.ref, 'refs/heads/develop')
steps:
# 최신 이미지를 pull
@@ -97,13 +94,6 @@ jobs:
echo "next_version=${NEXT_VERSION}" >> $GITHUB_ENV
echo "port=${PORT}" >> $GITHUB_ENV
- # 기존의 동일한 컨테이너를 중지 (블루/그린 중 다음 배포 타겟에 해당하는 컨테이너)
- - name: Stop current docker container
- run: |
- echo "Stopping container inplace-${{ env.next_version }}"
- sudo docker stop inplace-${{ env.next_version }} || true
- sudo docker rm inplace-${{ env.next_version }} || true
-
# .env 파일 생성
- name: Create .env file
run: |
@@ -116,8 +106,27 @@ jobs:
echo "Running new container inplace-${{ env.next_version }}"
sudo docker run --name "inplace-${{ env.next_version }}" --rm -d -p ${{ env.port }}:8080 --env-file .env ${{ secrets.DOCKERHUB_USERNAME }}/inplace
- # 로드 밸런서 타겟 그룹을 업데이트하여 트래픽을 새 컨테이너로 전환합니다.
+ # 스프링부트 시작을 기다리기 위해 헬스 체크로 대기
+ - name: Wait for Spring Boot Application to Start
+ run: |
+ echo "Waiting for the application to be healthy on PORT: ${{ env.port }}..."
+ for i in {1..40}; do
+ if curl -s http://localhost:${{ env.port }}/actuator/health | grep '"status":"UP"' > /dev/null; then
+ echo "Application is healthy"
+ break
+ fi
+ echo "Waiting for new application to start...($i)"
+ sleep 5
+ done
+
+ if [ $i -eq 40 ]; then
+ echo "Application failed to start after 40 attempts."
+ exit 1
+ fi
+
+ # 로드 밸런서 타겟 그룹을 업데이트하여 트래픽을 새 컨테이너로 전환
- name: Update Load Balancer Target Group
+ if: success()
run: |
TARGET_GROUP_ARN_BLUE="${{ secrets.TARGET_GROUP_ARN_8080 }}"
TARGET_GROUP_ARN_GREEN="${{ secrets.TARGET_GROUP_ARN_8081 }}"
@@ -127,6 +136,14 @@ jobs:
aws elbv2 modify-listener --listener-arn ${{ secrets.LISTENER_ARN_443 }} --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN_GREEN
fi
+ # 이전 컨테이너 정리 전 드레이닝 시간 부여
+ - name: Wait for connection draining
+ run: |
+ if [[ "${{ env.current_version }}" != "none" ]]; then
+ echo "Waiting 30 seconds for connection draining..."
+ sleep 30
+ fi
+
# 이전 컨테이너 정리
- name: Remove previous docker container
run: |
diff --git a/README.md b/README.md
index b52c56a7..ca71f403 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,814 @@
-# Team7_BE
+# Inplace ( Team7_BE )
-## Project Version
+
+
+
-- Spring Boots 3.3.3
-- Java 17 LTS
\ No newline at end of file
+# 목차
+
+1. [**프로젝트 개요**](#-프로젝트-개요)
+ > 1.1 [**프로젝트 목적**](#-프로젝트-목적)
+ >
+ > 1.2 [**서비스 정보**](#-서비스-정보)
+ >
+ > 1.3 [**배포 주소**](#-배포-주소)
+
+2. [**프로젝트 구조**](#-프로젝트-구조)
+ > 2.1 [**프로그램 구조도**](#-프로그램-구조도)
+ >
+ > 2.2 [**API & ERD**](#-api--erd)
+ >
+ > 2.3 [**패키지 구조**](#패키지-구조)
+
+3. [**개발 정보**](#-개발-정보)
+ > 3.1 [**개발 기간**](#-개발-기간)
+ >
+ > 3.2 [**팀원**](#-팀원)
+ >
+ > 3.3 [**컨벤션**](#컨벤션)
+ >
+ > 3.4 [**브랜치 전략**](#브랜치-전략)
+ >
+ > 3.5 [**저장소 정보**](#-저장소-정보)
+
+4. [**기술 정보**](#-기술-정보)
+ > 4.1 [**주요 종속성 버전**](#-주요-종속성-버전)
+ >
+ > 4.2 [**기술 스택**](#-기술-스택)
+
+5. [**테스트**](#-테스트)
+
+6. [**기능 및 사용 예시**](#-기능-및-사용-예시)
+ > 6.1 [**세부 기능 흐름**](#-세부-기능-흐름)
+ >
+ > 6.2 [**사용 예시**](#-사용-예시)
+
+---
+
+# 📝 프로젝트 개요
+
+## 🙌 프로젝트 목적
+
+> 저희의 아이디어는 **데이트 코스의 단조로움**을 어떻게 하면 해소할 수 있을까? 💡 라는 생각에서 시작했습니다.
+>
+> 아이디어를 구체화하는 과정에서 저희는 **장소**에 관련된 소재로 인스타, 유튜브 등의 SNS 및 동영상 플랫폼 📱의 성장과 함께 등장한 **인플루언서** 라는 개념에 집중하게
+> 되었습니다.
+>
+> 이는 **인플루언서가 방문한 장소에 대한 정보를 서비스 해보자!** 📍 라는 생각으로 이어졌습니다.
+>
+> 이를 통해 사용자가 관심있는 **인플루언서를 등록하고, 이에 따른 장소 추천 및, 장소에 대한 리뷰 기능** 📋을 제공하여, **Inplace** 라는 저희만의 웹
+> 애플리케이션으로 구현해보았습니다.
+
+## 📋 서비스 정보
+
+> 긴 영상은 필요 없어요 인플루언서가 다녀간 쿨플, 한눈에 쏙!
+
+1. **회원가입 및 로그인**
+ - Spring Security를 이용한 OAuth 2.0 카카오 로그인 기능을 사용합니다
+ - 쿠키에 Access, Refresh Token을 담아 사용하며, Refresh 동작을 수행할 수 있습니다
+2. **현재 위치 기반 서비스**
+ - 웹 페이지의 위치 정보 사용에 동의시, 카카오 API와 내 위치 정보를 사용하여 주변의 장소 정보와, 자동 지도 위치 설정을 사용할 수 있습니다
+3. **통합 검색 서비스**
+ - Elastic Search를 사용한 인플루언서, 장소, 비디오 이름에 대한 통합 검색 기능을 사용할 수 있습니다
+4. **관심 인플루언서 등록 및 이를 토대로 한 서비스**
+ - 최초 로그인 시 & 인플루언서 페이지에서 관심 인플루언서를 등록할 수 있습니다
+ - 이를 토대로 메인 페이지에서 관심 인플루언서의 최신 방문 장소를 확인할 수 있습니다
+5. **지도 기반 검색 서비스**
+ - 지도 API를 이용하여 장소를 검색할 수 있습니다
+ - 관심 등록하지 않은 인플루언서 및 주소, 장소 태그를 이용하여 세부 검색이 가능합니다
+6. **장소 세부 정보 서비스**
+ - 장소의 세부 정보를 열람할 수 있습니다
+ - 장소에 달린 다른 유저들의 리뷰를 확인할 수 있습니다
+ - 장소에 대한 좋아요 기능을 사용할 수 있습니다
+ - 장소 세부 페이지에서 장소에 대한 정보를 카카오톡 메세지로 받아 볼 수 있습니다
+7. **리뷰 기능**
+ - 장소 정보를 받은 후 3일 뒤, 해당 장소에 대한 리뷰 링크를 받아 리뷰를 작성할 수 있습니다
+ - 위 기능들은 카카오톡 메세지 보내기 API를 이용하며, 카카오톡으로 전송되는 링크는 모바일 뷰를 지원합니다
+8. **마이 페이지 기능**
+ - 좋아요 표시한 장소, 인플루언서를 관리할 수 있습니다
+ - 내가 작성한 리뷰를 관리할 수 있습니다
+ - 사용자 닉네임을 변경할 수 있습니다
+
+## 🌐 배포 주소
+
+> **BackEnd** : [**_api.inplace.my_**](https://api.inplace.my)
+>
+> **FrontEnd**: [**_inplace.my_**](https://inplace.my)
+
+---
+
+# 🏗️ 프로젝트 구조
+
+## 🖼️ 프로그램 구조도
+
+### CI CD
+
+![Backend_CICD](https://github.com/user-attachments/assets/f7db576e-5150-443c-8d2f-ab232294c296)
+
+### System Architecture
+
+![Architecture](https://github.com/user-attachments/assets/b21a697a-003e-4371-bdde-e12db6dbc1d9)
+
+## 📊 API & ERD
+
+![image](https://github.com/user-attachments/assets/ce11ff39-8294-4eda-aa58-7b5bd0ac1620)
+
+- [🚗 Visit Team7 API](https://www.notion.so/API-9e96d1ef1475414b861a50d0e4ca366e)
+
+![image](https://github.com/user-attachments/assets/333c9f3b-6678-48a6-b32d-b9b36d8cb182)
+
+- [🙋♂️ Visit Team7 ERD](https://www.notion.so/ERD-36ec8e40cb264abe87588e97ae77ac55)
+
+## 🗂️패키지 구조
+
+ 패키지 구조
+
+
+```
+📦src
+ ┣ 📂main
+ ┃ ┣ 📂generated
+ ┃ ┃ ┗ 📂team7
+ ┃ ┃ ┃ ┗ 📂inplace
+ ┃ ┃ ┃ ┃ ┣ 📂crawling
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QYoutubeChannel.java
+ ┃ ┃ ┃ ┃ ┣ 📂favoriteInfluencer
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QFavoriteInfluencer.java
+ ┃ ┃ ┃ ┃ ┣ 📂global
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂exception
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QErrorLog.java
+ ┃ ┃ ┃ ┃ ┣ 📂influencer
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QInfluencer.java
+ ┃ ┃ ┃ ┃ ┣ 📂likedPlace
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QLikedPlace.java
+ ┃ ┃ ┃ ┃ ┣ 📂oauthToken
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QOauthToken.java
+ ┃ ┃ ┃ ┃ ┣ 📂place
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜QAddress.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜QCoordinate.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜QMenu.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜QOffDay.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜QOpenTime.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QPlace.java
+ ┃ ┃ ┃ ┃ ┣ 📂review
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QReview.java
+ ┃ ┃ ┃ ┃ ┣ 📂user
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QUser.java
+ ┃ ┃ ┃ ┃ ┗ 📂video
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QVideo.java
+ ┃ ┣ 📂java
+ ┃ ┃ ┗ 📂team7
+ ┃ ┃ ┃ ┗ 📂inplace
+ ┃ ┃ ┃ ┃ ┣ 📂admin
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂banner
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂command
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜BannerCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜BannerInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜BannerService.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜Banner.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜BannerRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜BannerS3Repository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜BannerController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜BannerRequest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜BannerResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂cicd
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜TestController.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂crawling
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜CrawlingInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AddressUtil.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CrawlingFacade.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜KakaoCrawlingService.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜VideoCrawlingService.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜YoutubeCrawlingService.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂client
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceNode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜KakaoMapClient.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜YoutubeClient.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ChannelType.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜YoutubeChannel.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜YoutubeChannelRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜CrawlingController.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂error
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ErrorLog.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ErrorLogController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ErrorLogRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📜AdminPageController.java
+ ┃ ┃ ┃ ┃ ┣ 📂favoriteInfluencer
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜FavoriteInfluencerCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜FavoriteInfluencerListCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜FavoriteInfluencerService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜FavoriteInfluencer.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistent
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜FavoriteInfluencerRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerLikeRequest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerListLikeRequest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜FavoriteInfluencerController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜FavoriteInfluencerControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┣ 📂global
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂annotation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜Facade.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂exception
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂code
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AuthorizationErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜BannerErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ChannelErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜LikedPlaceErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ReviewErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜UserErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoErrorCode.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InplaceException.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InplaceExceptionHandler.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂kakao
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📂config
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜KakaoApiProperties.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂queryDsl
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜QueryDslConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂rest
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜RestTemplateConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜WebClientConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂scheduler
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ScheduledExecutorConfig.java
+ ┃ ┃ ┃ ┃ ┣ 📂influencer
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerNameInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜Influencer.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerNameResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerRequest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┣ 📂infra
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂s3
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AwsProperties.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜S3Config.java
+ ┃ ┃ ┃ ┃ ┣ 📂likedPlace
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜LikedPlace.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜LikedPlaceRepository.java
+ ┃ ┃ ┃ ┃ ┣ 📂oauthToken
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂command
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜OauthTokenCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜OauthTokenService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜OauthToken.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜OauthTokenRepository.java
+ ┃ ┃ ┃ ┃ ┣ 📂place
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂command
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceLikeCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlacesCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CategoryInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜LikedPlaceInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceDetailInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceForVideo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CategoryService.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Address.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Category.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Coordinate.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Menu.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜OffDay.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜OpenTime.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜Place.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceCustomRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceCustomRepositoryImpl.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CategoriesResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceDetailResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceLikeRequest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlacesResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ReviewRequest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┣ 📂placeMessage
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂command
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceMessageCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜KakaoMessageService.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceMessageFacade.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceMessageController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceMessageControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂util
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Button.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Content.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜FeedTemplate.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜KakaoMessageMaker.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Link.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜LocationTemplate.java
+ ┃ ┃ ┃ ┃ ┣ 📂review
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜MyReviewInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ReviewCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜Review.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ReviewController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┣ 📂search
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AutoCompletionInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceSearchInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜SearchType.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜SearchService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂config
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜MatchAgainstFunctionContributor.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜SearchResult.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerSearchRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜PlaceSearchRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SearchRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoSearchRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SearchController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜SearchControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┣ 📂security
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CustomOAuth2User.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜KakaoOAuthResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CurrentUserProvider.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜CustomOAuth2UserService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂config
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CorsConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜JwtProperties.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜RedisConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SecurityConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SecurityEntryPointConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SecurityFilterConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SecurityHandlerConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜SecurityServiceConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜SecurityUtilConfig.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂entryPoint
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜LoginAuthenticationEntryPoint.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂filter
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AuthorizationFilter.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜ExceptionHandlingFilter.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜TokenType.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂handler
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CustomAccessDeniedHandler.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CustomFailureHandler.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜CustomSuccessHandler.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂util
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AuthorizationUtil.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CookieUtil.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜JwtUtil.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜TokenEncryptionUtil.java
+ ┃ ┃ ┃ ┃ ┣ 📂token
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜TokenCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜RefreshTokenFacade.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜RefreshTokenService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜RefreshToken.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜RefreshTokenRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜RefreshTokenController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜RefreshTokenControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┣ 📂user
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜UserCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜UserInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜UserFacade.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜UserService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Role.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜User.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜UserType.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜UserRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜LikedInfluencerResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜LikedPlaceResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜MyReviewResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜UserInfoResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜TempController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜UserController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜UserControllerApiSepc.java
+ ┃ ┃ ┃ ┃ ┣ 📂video
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂command
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoCommand.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoInfo.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AliasUtil.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜Template.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜VideoFacade.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoService.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜Video.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoRepository.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂presentation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📂dto
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜VideoResponse.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoSearchParams.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜VideoController.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoControllerApiSpec.java
+ ┃ ┃ ┃ ┃ ┗ 📜InplaceApplication.java
+ ┃ ┗ 📂resources
+ ┃ ┃ ┣ 📂META-INF
+ ┃ ┃ ┃ ┗ 📂services
+ ┃ ┃ ┃ ┃ ┗ 📜org.hibernate.boot.model.FunctionContributor
+ ┃ ┃ ┣ 📂sql
+ ┃ ┃ ┃ ┣ 📜data.sql
+ ┃ ┃ ┃ ┗ 📜schema.sql
+ ┃ ┃ ┣ 📂static
+ ┃ ┃ ┃ ┣ 📂css
+ ┃ ┃ ┃ ┃ ┣ 📜banner.css
+ ┃ ┃ ┃ ┃ ┣ 📜error_logs_style.css
+ ┃ ┃ ┃ ┃ ┣ 📜main.css
+ ┃ ┃ ┃ ┃ ┗ 📜style.css
+ ┃ ┃ ┃ ┣ 📂js
+ ┃ ┃ ┃ ┃ ┣ 📜banner.js
+ ┃ ┃ ┃ ┃ ┣ 📜error-logs.js
+ ┃ ┃ ┃ ┃ ┣ 📜main.js
+ ┃ ┃ ┃ ┃ ┗ 📜video.js
+ ┃ ┃ ┃ ┗ 📜favicon.ico
+ ┃ ┃ ┣ 📂templates
+ ┃ ┃ ┃ ┣ 📂admin
+ ┃ ┃ ┃ ┃ ┣ 📜banner.html
+ ┃ ┃ ┃ ┃ ┣ 📜error-logs.html
+ ┃ ┃ ┃ ┃ ┣ 📜main.html
+ ┃ ┃ ┃ ┃ ┗ 📜video.html
+ ┃ ┃ ┃ ┗ 📂introduce
+ ┃ ┃ ┃ ┃ ┗ 📜main.html
+ ┃ ┃ ┣ 📜application-db.yaml
+ ┃ ┃ ┣ 📜application-kakao.yaml
+ ┃ ┃ ┣ 📜application-redis.yaml
+ ┃ ┃ ┣ 📜application-s3.yaml
+ ┃ ┃ ┣ 📜application-security.yaml
+ ┃ ┃ ┣ 📜application-youtube.yaml
+ ┃ ┃ ┗ 📜application.yaml
+ ┗ 📂test
+ ┃ ┗ 📂java
+ ┃ ┃ ┗ 📂team7
+ ┃ ┃ ┃ ┗ 📂inplace
+ ┃ ┃ ┃ ┃ ┣ 📂config
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂annotation
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜CustomRepositoryTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂crawling
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜AddressUtilTest.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂client
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜KakaoMapClientTest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜YoutubeClientTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂influencer
+ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerControllerTest.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📜InfluencerRepositoryTest.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📜InfluencerServiceTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂likedPlace
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜LikedPlaceRepositoryTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂place
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceServiceTest.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜PlaceRepositoryTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂review
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewRepositoryTest.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📜ReviewServiceTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂security
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂config
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜CorsTest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜SecurityConfigTest.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂util
+ ┃ ┃ ┃ ┃ ┃ ┃ ┣ 📜AuthorizationUtilTest.java
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜JwtUtilTest.java
+ ┃ ┃ ┃ ┃ ┣ 📂util
+ ┃ ┃ ┃ ┃ ┃ ┗ 📜TestUtil.java
+ ┃ ┃ ┃ ┃ ┣ 📂video
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂application
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoServiceTest.java
+ ┃ ┃ ┃ ┃ ┃ ┣ 📂domain
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoTest.java
+ ┃ ┃ ┃ ┃ ┃ ┗ 📂persistence
+ ┃ ┃ ┃ ┃ ┃ ┃ ┗ 📜VideoRepositoryTest.java
+ ┃ ┃ ┃ ┃ ┗ 📜InplaceApplicationTests.java
+```
+
+
+
+---
+
+# 👨💻 개발 정보
+
+## 📅 개발 기간
+
+> **_2024.08.22 ~ 2024.11.15_**
+
+## 👥 팀원
+
+
+
+
+
+ Frontend
+ |
+
+ Frontend
+ |
+
+
+
+ 이정민
+ |
+
+ 이효은
+ |
+
+
+
+
+
+ Backend
+ |
+
+ Backend
+ |
+
+ Backend
+ |
+
+ Backend
+ |
+
+ Backend
+ |
+
+
+
+ 이상희
+ |
+
+ 김동윤
+ |
+
+ 정수현
+ |
+
+ 우현서
+ |
+
+ 배준호
+ |
+
+
+
+
+## 컨벤션
+
+> [**Commit Convention
+**](https://github.com/kakao-tech-campus-2nd-step3/Team7_BE/wiki/Commit-%EC%BB%A8%EB%B2%A4%EC%85%98)
+>
+> [**PR Convention**](https://github.com/kakao-tech-campus-2nd-step3/Team7_BE/wiki/PR-%EC%BB%A8%EB%B2%A4%EC%85%98)
+>
+> [**Issue Convention
+**](https://github.com/kakao-tech-campus-2nd-step3/Team7_BE/wiki/%EC%9D%B4%EC%8A%88-%EC%BB%A8%EB%B2%A4%EC%85%98)
+>
+> [**Coding Convention
+**](https://github.com/kakao-tech-campus-2nd-step3/Team7_BE/wiki/%EC%BD%94%EB%94%A9-%EC%BB%A8%EB%B2%A4%EC%85%98)
+>
+> [**브랜치 전략
+**](https://github.com/kakao-tech-campus-2nd-step3/Team7_BE/wiki/%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%84%EB%9E%B5)
+>
+💡 *컨벤션과
+브랜치전략은 [Notiion](https://quickest-asterisk-75d.notion.site/7-7-05d799c3f023443f8fb66abf97ca5965)
+에서도 확인이 가능합니다!*
+
+## 📂 저장소 정보
+
+> **Using Language**
+>
+![GitHub language count](https://img.shields.io/github/languages/count/kakao-tech-campus-2nd-step3/Team7_BE)
+![GitHub top language](https://img.shields.io/github/languages/top/kakao-tech-campus-2nd-step3/Team7_BE)
+
+> **Repo, Code Volume**
+>
+![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/kakao-tech-campus-2nd-step3/Team7_BE)
+![GitHub repo size](https://img.shields.io/github/repo-size/kakao-tech-campus-2nd-step3/Team7_BE)
+
+> **Commit Avg**
+>
+![GitHub commit activity](https://img.shields.io/github/commit-activity/w/kakao-tech-campus-2nd-step3/Team7_BE)
+
+> **Issues**
+>
+![GitHub open issues](https://img.shields.io/github/issues/kakao-tech-campus-2nd-step3/Team7_BE)
+![GitHub closed issues](https://img.shields.io/github/issues-closed/kakao-tech-campus-2nd-step3/Team7_BE)
+
+> **PRs**
+>
+![GitHub pull requests](https://img.shields.io/github/issues-pr/kakao-tech-campus-2nd-step3/Team7_BE?label=open%20pull%20requests)
+![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/kakao-tech-campus-2nd-step3/Team7_BE?label=closed%20pull%20requests)
+
+---
+
+# 🛠️ 기술 정보
+
+## 🧩 주요 종속성 버전
+
+> **Spring Boots 3.3.3**
+>
+> **Java 17 LTS**
+>
+> **AWS**
+> Spring-Cloud-Starter-AWS:2.2.6.RELEASE
+>
+> **JWT**
+>
+> JJWT-API:0.12.3
+>
+> JJWT-IMPL:0.12.3
+>
+> JJWT-JACKSON:0.12.3
+>
+> **SpringDoc(Swagger)**
+>
+> SpringDoc-OpenApi-Starter-WebMvc-UI: 2.0.3
+>
+> **QueryDSL**
+>
+> QueryDsl-JPA: 5.0.0(Jakarata)
+>
+> **Spring Starter는 Spring버전을 따릅니다**
+>
+> spring-boot-starter-security
+>
+> spring-boot-starter-oauth2-client
+>
+> spring-boot-starter-data-jpa
+>
+> spring-boot-starter-data-redis
+>
+> spring-boot-starter-thymeleaf
+
+## 🚀 기술 스택
+
+> **Backend**
+>
+![Spring Boot](https://img.shields.io/badge/Spring%20Boot-6DB33F?style=flat-square&logo=springboot&logoColor=white)
+![Lombok](https://img.shields.io/badge/Lombok-DC382D?style=flat-square&logo=lombok&logoColor=white)
+
+> **Security**
+>
+![Spring Security](https://img.shields.io/badge/Spring%20Security-6DB33F?style=flat-square&logo=springsecurity&logoColor=white)
+![JWT](https://img.shields.io/badge/JWT-000000?style=flat-square&logo=jsonwebtokens&logoColor=white)
+
+> **DB**
+>
+![Spring Data JPA](https://img.shields.io/badge/Spring%20Data%20JPA-6DB33F?style=flat-square&logo=spring&logoColor=white)
+![QueryDSL](https://img.shields.io/badge/QueryDSL-0055a2?style=flat-square&logo=appveyor&logoColor=white)
+![MySQL](https://img.shields.io/badge/MySQL-4479A1?style=flat-square&logo=mysql&logoColor=white)
+![Redis](https://img.shields.io/badge/Redis-DC382D?style=flat-square&logo=redis&logoColor=white)
+
+> **Web**
+>
+![Spring WebFlux](https://img.shields.io/badge/Spring%20WebFlux-6DB33F?style=flat-square&logo=spring&logoColor=white)
+
+> **Deployment**
+>
+![AWS EC2](https://img.shields.io/badge/AWS%20EC2-FF9900?style=flat-square&logo=amazonaws&logoColor=white)
+![Docker](https://img.shields.io/badge/Docker-2496ED?style=flat-square&logo=docker&logoColor=white)
+
+> **Admin Page**
+>
+![jQuery](https://img.shields.io/badge/jQuery-0769AD?style=flat-square&logo=jquery&logoColor=white)
+![Thymeleaf](https://img.shields.io/badge/Thymeleaf-005F0F?style=flat-square&logo=thymeleaf&logoColor=white)
+![AJAX](https://img.shields.io/badge/AJAX-005571?style=flat-square&logo=ajax&logoColor=white)
+
+> **Test**
+>
+![JMeter](https://img.shields.io/badge/JMeter-D22128?style=flat-square&logo=apachejmeter&logoColor=white)
+![JUnit](https://img.shields.io/badge/JUnit-25A162?style=flat-square&logo=junit5&logoColor=white)
+![Mockito](https://img.shields.io/badge/Mockito-FFCA28?style=flat-square&logo=mockito&logoColor=white)
+
+> **Code Maintenance**
+>
+![Git](https://img.shields.io/badge/Git-F05032?style=flat-square&logo=git&logoColor=white)
+![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)
+![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-2088FF?style=flat-square&logo=githubactions&logoColor=white)
+
+> **Collaboration Tool**
+>
+![Discord](https://img.shields.io/badge/Discord-5865F2?style=flat-square&logo=discord&logoColor=white)
+![Slack](https://img.shields.io/badge/Slack-4A154B?style=flat-square&logo=slack&logoColor=white)
+
+---
+
+# ✅ 테스트
+
+> **테스트 시나리오** : https://www.notion.so/9ed68b292c004fc69f7eaad513054d96
+>
+> **테스트 결과보고서** : https://www.notion.so/08a520d3b8c44154a19425b0bcc16f6f
+
+---
+
+# 🎬 기능 및 사용 예시
+
+## 🔍 세부 기능 흐름
+
+- ### Spring Security
+ - oauth 로그인 시, jwt로 accessToken과 refreshToken을 Cookie에 담아줍니다.
+ - oauthToken은 aes알고리즘으로 암호화 되어 db에 저장됩니다.
+ - 모든 요청은 AuthorizationFilter에서 Cookie에 있는 토큰이 유효한지 확인하고, 유효하면 Authenticate합니다.
+- ### Influencer
+ - 로그인 상태인 경우 사용자가 좋아요한 인플루언서를 먼저 반환합니다.
+- ### FavoriteInfluencer
+ - 로그인 상태를 확인한 후 좋아요/ 싫어요 요청을 처리합니다.
+ - 내가 좋아요한 인플루언서 정보를 반환합니다.
+- ### Place
+ - places에 videos, influecers 테이블을 Left join하고 지도 범위 내에있는 장소를 추려냅니다.
+ - 카테고리와 인플루언서 이름으로 필터링하고, 사용자와 가까운순으로 정렬 후 반환합니다.
+ - 특정 장소에 대한 요청이 들어오면 세부 정보와 함께 관련된 인플루언서, 비디오와 사용자 리뷰 정보를 추가하여 반환합니다.
+ - 내가 좋아요한 장소 조회 시 인플루언서 정보 일부를 함께 반환합니다.
+ - PlaceMessage
+ - 요청이 오면 webClient(비동기)로 나에게보내기 kakao api를 통해 장소 정보를 보냅니다.
+ - 3일 뒤 나에게 보내기 kakao api를 통해 리뷰 요청 메세지를 보냅니다.
+- ### LikedPlace
+ - 사용자가 장소에 좋아요를 누르거나 취소하면 상태를 업데이트합니다.
+ - 기존 정보가 존재하지 않으면 새로 생성후 저장합니다.
+- ### Video
+ - 로그인 상태인 경우 사용자가 좋아요한 인플루언서의 동영상, 사용자 주변 장소의 동영상을 반환합니다.
+ - 로그인 상태가 아닌 경우 조회수 증가량이 높은 동영상, 새로운 동영상을 반환합니다.
+- ### Review
+ - 로그인 상태를 확인한 후 리뷰 추가, 리뷰 삭제 요청을 처리합니다.
+ - 장소별 리뷰 조회 시 본인이 작성한 리뷰인지 여부를 포함하여 반환합니다.
+ - 내가 작성한 리뷰 조회 시 장소 정보 일부를 함께 반환합니다.
+- ### User
+ - User nickname을 더티 체킹을 통해 변경한다.
+- ### TokenRefresh
+ - Redis DB에 username(key)로 refreshToken을 확인하고, RTR (Refresh Token Rotation)을 합니다.
+- ### videoCrawling
+ - DB에서 인플루언서 유튜브 채널을 가져와 새로 업데이트 된 정보 크롤링해서 정규표현식으로 주소정보를 추출합니다.
+ - Video에서 장소정보가 추출되면 kakaoApi를 통해 매장 정보를 가져와 인플루언서별로 트랜잭션을 분리해 장소정보를 저장합니다.
+ - Video에서 장소정보가 추출되지 않으면 Admin페이지를 통해 수동으로 장소 정보를 입력합니다.
+- ### AdminPage
+ - 배너, 에러로그, 강제크롤링 기능을 제공합니다.
+ - 홈페이지에 배너를 등록하거나 삭제할 수 있습니다.
+ - 서버 내에서 발생한 500번대 에러로그와 StackTrace를 확인할 수 있습니다.
+ - 스케쥴링 되어있는 비디오 크롤링을 강제로 실행할 수 있습니다.
+ - 스케쥴링 되어있는 조회수 크롤링을 강제로 실행할 수 있습니다.
+
+## 📱 사용 예시
+
+> 실제 유저 사용 페이지 흐름 보여주기 ( 영상 x )
diff --git a/build.gradle b/build.gradle
index 37c3cf6d..c1f7de09 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,9 +28,11 @@ dependencies {
//Spring
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
+ implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
testImplementation 'io.projectreactor:reactor-test'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
+ implementation 'org.springframework.boot:spring-boot-starter-actuator'
//Security
implementation 'org.springframework.boot:spring-boot-starter-security'
diff --git a/prod.env b/prod.env
new file mode 100644
index 00000000..efdecbd5
--- /dev/null
+++ b/prod.env
@@ -0,0 +1,17 @@
+KAKAO_CLIENT_ID=059dc1f42bbce3a6499c8c35e806b59e
+KAKAO_CLIENT_SECRET=XhG4I7doMSAPynuSj3YeCUJfHMGflQZ7
+KAKAO_REDIRECT_URI={baseUrl}/login/oauth2/code/kakao
+
+JWT_SECRET=1234567890
+JWT_ACCESS_TOKEN_EXPIRED_TIME=300000
+JWT_REFRESH_TOKEN_EXPIRED_TIME=60
+
+YOUTUBE_API_KEY=AIzaSyCVxk43oKFnjbMacGK7m9MqPzsU5-736Ok
+KAKAO_REST_API_KEY=89fb1518340b58930403f620b14e697d
+
+DATABASE_URL=jdbc:mysql://inplace-database.cnqma68ee3ul.ap-northeast-2.rds.amazonaws.com:3306/inplace
+DATABASE_USERNAME=admin
+DATABASE_PASSWORD=tanghulu123!
+
+REDIS_DATABASE_URL=localhost
+REDIS_DATABASE_PORT=6379
\ No newline at end of file
diff --git a/src/main/generated/team7/inplace/admin/banner/domain/QBanner.java b/src/main/generated/team7/inplace/admin/banner/domain/QBanner.java
new file mode 100644
index 00000000..64578408
--- /dev/null
+++ b/src/main/generated/team7/inplace/admin/banner/domain/QBanner.java
@@ -0,0 +1,47 @@
+package team7.inplace.admin.banner.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+
+
+/**
+ * QBanner is a Querydsl query type for Banner
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QBanner extends EntityPathBase {
+
+ private static final long serialVersionUID = -455211757L;
+
+ public static final QBanner banner = new QBanner("banner");
+
+ public final DateTimePath endDate = createDateTime("endDate", java.time.LocalDateTime.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final StringPath imgName = createString("imgName");
+
+ public final StringPath imgPath = createString("imgPath");
+
+ public final BooleanPath isFixed = createBoolean("isFixed");
+
+ public final DateTimePath startDate = createDateTime("startDate", java.time.LocalDateTime.class);
+
+ public QBanner(String variable) {
+ super(Banner.class, forVariable(variable));
+ }
+
+ public QBanner(Path extends Banner> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QBanner(PathMetadata metadata) {
+ super(Banner.class, metadata);
+ }
+
+}
+
diff --git a/src/main/generated/team7/inplace/crawling/domain/QYoutubeChannel.java b/src/main/generated/team7/inplace/admin/crawling/domain/QYoutubeChannel.java
similarity index 92%
rename from src/main/generated/team7/inplace/crawling/domain/QYoutubeChannel.java
rename to src/main/generated/team7/inplace/admin/crawling/domain/QYoutubeChannel.java
index 7b379620..9bba9349 100644
--- a/src/main/generated/team7/inplace/crawling/domain/QYoutubeChannel.java
+++ b/src/main/generated/team7/inplace/admin/crawling/domain/QYoutubeChannel.java
@@ -1,4 +1,4 @@
-package team7.inplace.crawling.domain;
+package team7.inplace.admin.crawling.domain;
import static com.querydsl.core.types.PathMetadataFactory.*;
@@ -15,7 +15,7 @@
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QYoutubeChannel extends EntityPathBase {
- private static final long serialVersionUID = 2131098359L;
+ private static final long serialVersionUID = -73514634L;
public static final QYoutubeChannel youtubeChannel = new QYoutubeChannel("youtubeChannel");
diff --git a/src/main/generated/team7/inplace/global/exception/QErrorLog.java b/src/main/generated/team7/inplace/admin/error/QErrorLog.java
similarity index 91%
rename from src/main/generated/team7/inplace/global/exception/QErrorLog.java
rename to src/main/generated/team7/inplace/admin/error/QErrorLog.java
index ceffad2a..498fdfb0 100644
--- a/src/main/generated/team7/inplace/global/exception/QErrorLog.java
+++ b/src/main/generated/team7/inplace/admin/error/QErrorLog.java
@@ -1,4 +1,4 @@
-package team7.inplace.global.exception;
+package team7.inplace.admin.error;
import static com.querydsl.core.types.PathMetadataFactory.*;
@@ -15,7 +15,7 @@
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QErrorLog extends EntityPathBase {
- private static final long serialVersionUID = 50351046L;
+ private static final long serialVersionUID = -245768895L;
public static final QErrorLog errorLog = new QErrorLog("errorLog");
diff --git a/src/main/generated/team7/inplace/likedPlace/domain/QLikedPlace.java b/src/main/generated/team7/inplace/likedPlace/domain/QLikedPlace.java
index 3657f5a9..092434c1 100644
--- a/src/main/generated/team7/inplace/likedPlace/domain/QLikedPlace.java
+++ b/src/main/generated/team7/inplace/likedPlace/domain/QLikedPlace.java
@@ -11,12 +11,12 @@
/**
- * QLikedPlace is a Querydsl query type for likedPlace
+ * QLikedPlace is a Querydsl query type for LikedPlace
*/
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QLikedPlace extends EntityPathBase {
- private static final long serialVersionUID = 1786130640L;
+ private static final long serialVersionUID = -2070471504L;
private static final PathInits INITS = PathInits.DIRECT2;
@@ -48,11 +48,8 @@ public QLikedPlace(PathMetadata metadata, PathInits inits) {
public QLikedPlace(Class extends LikedPlace> type, PathMetadata metadata, PathInits inits) {
super(type, metadata, inits);
- this.place = inits.isInitialized("place") ? new team7.inplace.place.domain.QPlace(
- forProperty("place"), inits.get("place")) : null;
- this.user =
- inits.isInitialized("user") ? new team7.inplace.user.domain.QUser(forProperty("user"))
- : null;
+ this.place = inits.isInitialized("place") ? new team7.inplace.place.domain.QPlace(forProperty("place"), inits.get("place")) : null;
+ this.user = inits.isInitialized("user") ? new team7.inplace.user.domain.QUser(forProperty("user")) : null;
}
}
diff --git a/src/main/java/team7/inplace/InplaceApplication.java b/src/main/java/team7/inplace/InplaceApplication.java
index 887c1415..143ddd36 100644
--- a/src/main/java/team7/inplace/InplaceApplication.java
+++ b/src/main/java/team7/inplace/InplaceApplication.java
@@ -11,11 +11,11 @@
@SpringBootApplication
@ConfigurationPropertiesScan
@EnableJpaRepositories(
- basePackages = "team7.inplace",
- excludeFilters = @ComponentScan.Filter(
- type = FilterType.ASSIGNABLE_TYPE,
- classes = {RefreshTokenRepository.class}
- )
+ basePackages = "team7.inplace",
+ excludeFilters = @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ classes = {RefreshTokenRepository.class}
+ )
)
public class InplaceApplication {
diff --git a/src/main/java/team7/inplace/admin/AdminPageController.java b/src/main/java/team7/inplace/admin/AdminPageController.java
index cfc69efa..f731c7e5 100644
--- a/src/main/java/team7/inplace/admin/AdminPageController.java
+++ b/src/main/java/team7/inplace/admin/AdminPageController.java
@@ -1,6 +1,5 @@
package team7.inplace.admin;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -9,12 +8,15 @@
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
-import team7.inplace.global.exception.ErrorLog;
-import team7.inplace.global.exception.ErrorLogRepository;
+import team7.inplace.admin.banner.persistence.BannerRepository;
+import team7.inplace.admin.error.ErrorLog;
+import team7.inplace.admin.error.ErrorLogRepository;
import team7.inplace.global.kakao.config.KakaoApiProperties;
import team7.inplace.video.domain.Video;
import team7.inplace.video.persistence.VideoRepository;
+import java.util.List;
+
@Controller
@RequiredArgsConstructor
@RequestMapping("/admin")
@@ -22,6 +24,7 @@ public class AdminPageController {
private final KakaoApiProperties kakaoApiProperties;
private final VideoRepository videoRepository;
private final ErrorLogRepository errorLogRepository;
+ private final BannerRepository bannerRepository;
@GetMapping("/video")
public String getVideos(@PageableDefault Pageable pageable, Model model) {
@@ -31,6 +34,7 @@ public String getVideos(@PageableDefault Pageable pageable, Model model) {
model.addAttribute("totalPages", videoPage.getTotalPages());
model.addAttribute("isFirst", videoPage.isFirst());
model.addAttribute("isLast", videoPage.isLast());
+ model.addAttribute("kakaoApiKey", kakaoApiProperties.jsKey());
return "admin/video.html";
}
@@ -40,4 +44,17 @@ public String getErrorLogs(Model model) {
model.addAttribute("errorLogs", errorLogs);
return "admin/error-logs.html";
}
+
+ @GetMapping("/banner")
+ public String getBanners(Model model) {
+ var banners = bannerRepository.findAll();
+
+ model.addAttribute("banners", banners);
+ return "admin/banner.html";
+ }
+
+ @GetMapping("/main")
+ public String getMainPage() {
+ return "admin/main.html";
+ }
}
diff --git a/src/main/java/team7/inplace/admin/banner/application/BannerService.java b/src/main/java/team7/inplace/admin/banner/application/BannerService.java
new file mode 100644
index 00000000..3faf4202
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/application/BannerService.java
@@ -0,0 +1,43 @@
+package team7.inplace.admin.banner.application;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import team7.inplace.admin.banner.application.command.BannerCommand.Create;
+import team7.inplace.admin.banner.application.dto.BannerInfo;
+import team7.inplace.admin.banner.application.dto.BannerInfo.Detail;
+import team7.inplace.admin.banner.persistence.BannerRepository;
+import team7.inplace.admin.banner.persistence.BannerS3Repository;
+import team7.inplace.global.exception.InplaceException;
+import team7.inplace.global.exception.code.BannerErrorCode;
+
+@Service
+@RequiredArgsConstructor
+public class BannerService {
+ private final BannerS3Repository bannerS3Repository;
+ private final BannerRepository bannerRepository;
+
+ public void uploadBanner(Create command) {
+ var imgPath = bannerS3Repository.uploadBanner(command.imageFile());
+ var banner = command.toEntity(imgPath);
+ bannerRepository.save(banner);
+ }
+
+ public List getBanners() {
+ var now = LocalDateTime.now();
+ var banners = bannerRepository.findActiveBanner(now);
+
+ return banners.stream()
+ .sorted((a, b) -> Boolean.compare(b.getIsFixed(), a.getIsFixed()))
+ .map(BannerInfo.Detail::from)
+ .toList();
+ }
+
+ public void deleteBanner(Long id) {
+ var banner = bannerRepository.findById(id)
+ .orElseThrow(() -> InplaceException.of(BannerErrorCode.NOT_FOUND));
+ bannerS3Repository.deleteBanner(banner.getImgPath());
+ bannerRepository.delete(banner);
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/application/command/BannerCommand.java b/src/main/java/team7/inplace/admin/banner/application/command/BannerCommand.java
new file mode 100644
index 00000000..ff4af7df
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/application/command/BannerCommand.java
@@ -0,0 +1,19 @@
+package team7.inplace.admin.banner.application.command;
+
+import java.time.LocalDateTime;
+import org.springframework.web.multipart.MultipartFile;
+import team7.inplace.admin.banner.domain.Banner;
+
+public class BannerCommand {
+ public record Create(
+ String imgName,
+ MultipartFile imageFile,
+ LocalDateTime startDate,
+ LocalDateTime endDate,
+ Boolean isFixed
+ ) {
+ public Banner toEntity(String imgPath) {
+ return Banner.of(imgName, imgPath, startDate, endDate, isFixed);
+ }
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/application/dto/BannerInfo.java b/src/main/java/team7/inplace/admin/banner/application/dto/BannerInfo.java
new file mode 100644
index 00000000..31b8900e
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/application/dto/BannerInfo.java
@@ -0,0 +1,14 @@
+package team7.inplace.admin.banner.application.dto;
+
+import team7.inplace.admin.banner.domain.Banner;
+
+public class BannerInfo {
+ public record Detail(
+ Long id,
+ String imageUrl
+ ) {
+ public static Detail from(Banner banner) {
+ return new Detail(banner.getId(), banner.getImgPath());
+ }
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/domain/Banner.java b/src/main/java/team7/inplace/admin/banner/domain/Banner.java
new file mode 100644
index 00000000..f7a1edd7
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/domain/Banner.java
@@ -0,0 +1,38 @@
+package team7.inplace.admin.banner.domain;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import java.time.LocalDateTime;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Banner {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String imgName;
+ private String imgPath;
+ private LocalDateTime startDate;
+ private LocalDateTime endDate;
+ private Boolean isFixed;
+
+ private Banner(String imgName, String imgPath, LocalDateTime startDate, LocalDateTime endDate, Boolean isFixed) {
+ this.imgName = imgName;
+ this.imgPath = imgPath;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.isFixed = isFixed;
+ }
+
+ public static Banner of(String imgName, String imgPath, LocalDateTime startDate, LocalDateTime endDate,
+ Boolean isFixed) {
+ return new Banner(imgName, imgPath, startDate, endDate, isFixed);
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/persistence/BannerRepository.java b/src/main/java/team7/inplace/admin/banner/persistence/BannerRepository.java
new file mode 100644
index 00000000..f8a21cc7
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/persistence/BannerRepository.java
@@ -0,0 +1,15 @@
+package team7.inplace.admin.banner.persistence;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import team7.inplace.admin.banner.domain.Banner;
+
+@Repository
+public interface BannerRepository extends JpaRepository {
+ @Query("SELECT l FROM Banner l WHERE l.startDate <= :now AND l.endDate >= :now or l.isFixed = true")
+ List findActiveBanner(@Param("now") LocalDateTime now);
+}
\ No newline at end of file
diff --git a/src/main/java/team7/inplace/admin/banner/persistence/BannerS3Repository.java b/src/main/java/team7/inplace/admin/banner/persistence/BannerS3Repository.java
new file mode 100644
index 00000000..58bb87cd
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/persistence/BannerS3Repository.java
@@ -0,0 +1,45 @@
+package team7.inplace.admin.banner.persistence;
+
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Repository;
+import org.springframework.web.multipart.MultipartFile;
+import team7.inplace.infra.s3.AwsProperties;
+
+@Repository
+@Slf4j
+@RequiredArgsConstructor
+public class BannerS3Repository {
+ private final AmazonS3Client amazonS3Client;
+ private final AwsProperties awsProperties;
+
+ public String uploadBanner(MultipartFile banner) {
+ var bucketName = awsProperties.bucketName();
+ var key = "banner/" + UUID.randomUUID();
+
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentLength(banner.getSize());
+ metadata.setContentType(banner.getContentType());
+
+ try {
+ amazonS3Client.putObject(bucketName, key, banner.getInputStream(), metadata);
+ return "https://d4oqudml6s9ih.cloudfront.net/" + key;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to upload banner", e);
+ }
+ }
+
+ public void deleteBanner(String imgPath) {
+ var bucketName = awsProperties.bucketName();
+ var key = imgPath.substring(imgPath.lastIndexOf("banner"));
+
+ try {
+ amazonS3Client.deleteObject(bucketName, key);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to delete banner", e);
+ }
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/presentation/BannerController.java b/src/main/java/team7/inplace/admin/banner/presentation/BannerController.java
new file mode 100644
index 00000000..0ba731be
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/presentation/BannerController.java
@@ -0,0 +1,43 @@
+package team7.inplace.admin.banner.presentation;
+
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import team7.inplace.admin.banner.application.BannerService;
+
+@RestController
+@Slf4j
+@RequestMapping("/banners")
+@RequiredArgsConstructor
+public class BannerController {
+ private final BannerService bannerService;
+
+ @PostMapping()
+ public void saveBanner(BannerRequest.Create request) {
+
+ bannerService.uploadBanner(request.toCommand());
+ }
+
+ @GetMapping()
+ public ResponseEntity> getBanners() {
+ var banners = bannerService.getBanners();
+
+ var response = banners.stream()
+ .map(BannerResponse.Info::from)
+ .toList();
+ return new ResponseEntity<>(response, HttpStatus.OK);
+ }
+
+ @DeleteMapping("/{id}")
+ public void deleteBanner(@PathVariable Long id) {
+ bannerService.deleteBanner(id);
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/presentation/BannerRequest.java b/src/main/java/team7/inplace/admin/banner/presentation/BannerRequest.java
new file mode 100644
index 00000000..8552327a
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/presentation/BannerRequest.java
@@ -0,0 +1,25 @@
+package team7.inplace.admin.banner.presentation;
+
+import java.time.LocalDate;
+import org.springframework.web.multipart.MultipartFile;
+import team7.inplace.admin.banner.application.command.BannerCommand;
+
+public class BannerRequest {
+ public record Create(
+ String imageName,
+ MultipartFile imageFile,
+ LocalDate startDate,
+ LocalDate endDate,
+ Boolean isFixed
+ ) {
+ public BannerCommand.Create toCommand() {
+ return new BannerCommand.Create(
+ imageName,
+ imageFile,
+ startDate.atStartOfDay(),
+ endDate.atStartOfDay(),
+ isFixed
+ );
+ }
+ }
+}
diff --git a/src/main/java/team7/inplace/admin/banner/presentation/BannerResponse.java b/src/main/java/team7/inplace/admin/banner/presentation/BannerResponse.java
new file mode 100644
index 00000000..fdb32e8a
--- /dev/null
+++ b/src/main/java/team7/inplace/admin/banner/presentation/BannerResponse.java
@@ -0,0 +1,14 @@
+package team7.inplace.admin.banner.presentation;
+
+import team7.inplace.admin.banner.application.dto.BannerInfo;
+
+public class BannerResponse {
+ public record Info(
+ Long id,
+ String imageUrl
+ ) {
+ public static Info from(BannerInfo.Detail banner) {
+ return new Info(banner.id(), banner.imageUrl());
+ }
+ }
+}
diff --git a/src/main/java/team7/inplace/cicd/TestController.java b/src/main/java/team7/inplace/admin/cicd/TestController.java
similarity index 90%
rename from src/main/java/team7/inplace/cicd/TestController.java
rename to src/main/java/team7/inplace/admin/cicd/TestController.java
index 34bcc01a..9d07e49a 100644
--- a/src/main/java/team7/inplace/cicd/TestController.java
+++ b/src/main/java/team7/inplace/admin/cicd/TestController.java
@@ -1,4 +1,4 @@
-package team7.inplace.cicd;
+package team7.inplace.admin.cicd;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
diff --git a/src/main/java/team7/inplace/crawling/application/AddressUtil.java b/src/main/java/team7/inplace/admin/crawling/application/AddressUtil.java
similarity index 94%
rename from src/main/java/team7/inplace/crawling/application/AddressUtil.java
rename to src/main/java/team7/inplace/admin/crawling/application/AddressUtil.java
index cfe07276..8bddfe09 100644
--- a/src/main/java/team7/inplace/crawling/application/AddressUtil.java
+++ b/src/main/java/team7/inplace/admin/crawling/application/AddressUtil.java
@@ -1,11 +1,12 @@
-package team7.inplace.crawling.application;
-
-import static lombok.AccessLevel.PRIVATE;
+package team7.inplace.admin.crawling.application;
import com.fasterxml.jackson.databind.JsonNode;
+import lombok.NoArgsConstructor;
+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import lombok.NoArgsConstructor;
+
+import static lombok.AccessLevel.PRIVATE;
@NoArgsConstructor(access = PRIVATE)
public final class AddressUtil {
diff --git a/src/main/java/team7/inplace/crawling/application/CrawlingFacade.java b/src/main/java/team7/inplace/admin/crawling/application/CrawlingFacade.java
similarity index 84%
rename from src/main/java/team7/inplace/crawling/application/CrawlingFacade.java
rename to src/main/java/team7/inplace/admin/crawling/application/CrawlingFacade.java
index 771eb3ef..1a6f1e95 100644
--- a/src/main/java/team7/inplace/crawling/application/CrawlingFacade.java
+++ b/src/main/java/team7/inplace/admin/crawling/application/CrawlingFacade.java
@@ -1,8 +1,9 @@
-package team7.inplace.crawling.application;
+package team7.inplace.admin.crawling.application;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import team7.inplace.crawling.application.dto.CrawlingInfo;
+import org.springframework.scheduling.annotation.Scheduled;
+import team7.inplace.admin.crawling.application.dto.CrawlingInfo;
import team7.inplace.global.annotation.Facade;
import team7.inplace.place.application.command.PlacesCommand;
import team7.inplace.video.application.VideoFacade;
@@ -16,7 +17,7 @@ public class CrawlingFacade {
private final KakaoCrawlingService kakaoCrawlingService;
private final VideoFacade videoFacade;
- //TODO: 스케쥴링 추가 예정
+ @Scheduled(cron = "0 0 2 * * *", zone = "Asia/Seoul")
public void updateVideos() {
var crawlingInfos = youtubeCrawlingService.crawlAllVideos();
for (var crawlingInfo : crawlingInfos) {
@@ -29,7 +30,7 @@ public void updateVideos() {
}
}
- //TODO: 스케쥴링 추가 예정
+ @Scheduled(cron = "0 30 2 * * *", zone = "Asia/Seoul")
public void updateVideoView() {
var crawlingInfos = videoCrawlingService.crawlingVideoView();
var videoCommands = crawlingInfos.stream()
diff --git a/src/main/java/team7/inplace/crawling/application/KakaoCrawlingService.java b/src/main/java/team7/inplace/admin/crawling/application/KakaoCrawlingService.java
similarity index 66%
rename from src/main/java/team7/inplace/crawling/application/KakaoCrawlingService.java
rename to src/main/java/team7/inplace/admin/crawling/application/KakaoCrawlingService.java
index a3a8cb56..f9d3fd2a 100644
--- a/src/main/java/team7/inplace/crawling/application/KakaoCrawlingService.java
+++ b/src/main/java/team7/inplace/admin/crawling/application/KakaoCrawlingService.java
@@ -1,9 +1,9 @@
-package team7.inplace.crawling.application;
+package team7.inplace.admin.crawling.application;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
-import team7.inplace.crawling.client.KakaoMapClient;
-import team7.inplace.crawling.client.dto.PlaceNode;
+import team7.inplace.admin.crawling.client.KakaoMapClient;
+import team7.inplace.admin.crawling.client.dto.PlaceNode;
@Service
@RequiredArgsConstructor
diff --git a/src/main/java/team7/inplace/crawling/application/VideoCrawlingService.java b/src/main/java/team7/inplace/admin/crawling/application/VideoCrawlingService.java
similarity index 73%
rename from src/main/java/team7/inplace/crawling/application/VideoCrawlingService.java
rename to src/main/java/team7/inplace/admin/crawling/application/VideoCrawlingService.java
index 45b0eb85..261df0a7 100644
--- a/src/main/java/team7/inplace/crawling/application/VideoCrawlingService.java
+++ b/src/main/java/team7/inplace/admin/crawling/application/VideoCrawlingService.java
@@ -1,13 +1,15 @@
-package team7.inplace.crawling.application;
+package team7.inplace.admin.crawling.application;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import team7.inplace.crawling.application.dto.CrawlingInfo;
-import team7.inplace.crawling.client.YoutubeClient;
+import team7.inplace.admin.crawling.application.dto.CrawlingInfo;
+import team7.inplace.admin.crawling.application.dto.CrawlingInfo.ViewInfo;
+import team7.inplace.admin.crawling.client.YoutubeClient;
import team7.inplace.video.persistence.VideoRepository;
+import java.util.List;
+
@Service
@RequiredArgsConstructor
public class VideoCrawlingService {
@@ -15,7 +17,7 @@ public class VideoCrawlingService {
private final YoutubeClient youtubeClient;
@Transactional(readOnly = true)
- public List crawlingVideoView() {
+ public List crawlingVideoView() {
var videos = videoRepository.findAll();
var videoInfos = videos.stream().map(video -> {
diff --git a/src/main/java/team7/inplace/crawling/application/YoutubeCrawlingService.java b/src/main/java/team7/inplace/admin/crawling/application/YoutubeCrawlingService.java
similarity index 89%
rename from src/main/java/team7/inplace/crawling/application/YoutubeCrawlingService.java
rename to src/main/java/team7/inplace/admin/crawling/application/YoutubeCrawlingService.java
index 1e47107f..3d1322b7 100644
--- a/src/main/java/team7/inplace/crawling/application/YoutubeCrawlingService.java
+++ b/src/main/java/team7/inplace/admin/crawling/application/YoutubeCrawlingService.java
@@ -1,18 +1,19 @@
-package team7.inplace.crawling.application;
+package team7.inplace.admin.crawling.application;
-import java.util.List;
-import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import team7.inplace.crawling.application.dto.CrawlingInfo;
-import team7.inplace.crawling.client.KakaoMapClient;
-import team7.inplace.crawling.client.YoutubeClient;
-import team7.inplace.crawling.persistence.YoutubeChannelRepository;
+import team7.inplace.admin.crawling.client.KakaoMapClient;
+import team7.inplace.admin.crawling.client.YoutubeClient;
+import team7.inplace.admin.crawling.persistence.YoutubeChannelRepository;
+import team7.inplace.admin.crawling.application.dto.CrawlingInfo;
import team7.inplace.global.exception.InplaceException;
import team7.inplace.global.exception.code.ChannelErrorCode;
+import java.util.List;
+import java.util.Objects;
+
@Slf4j
@Service
@RequiredArgsConstructor
diff --git a/src/main/java/team7/inplace/crawling/application/dto/CrawlingInfo.java b/src/main/java/team7/inplace/admin/crawling/application/dto/CrawlingInfo.java
similarity index 93%
rename from src/main/java/team7/inplace/crawling/application/dto/CrawlingInfo.java
rename to src/main/java/team7/inplace/admin/crawling/application/dto/CrawlingInfo.java
index 190a5a9b..82f72b53 100644
--- a/src/main/java/team7/inplace/crawling/application/dto/CrawlingInfo.java
+++ b/src/main/java/team7/inplace/admin/crawling/application/dto/CrawlingInfo.java
@@ -1,9 +1,9 @@
-package team7.inplace.crawling.application.dto;
+package team7.inplace.admin.crawling.application.dto;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
import java.util.Objects;
-import team7.inplace.crawling.client.dto.PlaceNode;
+import team7.inplace.admin.crawling.client.dto.PlaceNode;
import team7.inplace.place.application.command.PlacesCommand;
import team7.inplace.video.application.command.VideoCommand;
diff --git a/src/main/java/team7/inplace/crawling/client/KakaoMapClient.java b/src/main/java/team7/inplace/admin/crawling/client/KakaoMapClient.java
similarity index 96%
rename from src/main/java/team7/inplace/crawling/client/KakaoMapClient.java
rename to src/main/java/team7/inplace/admin/crawling/client/KakaoMapClient.java
index e5e2284d..95d952e1 100644
--- a/src/main/java/team7/inplace/crawling/client/KakaoMapClient.java
+++ b/src/main/java/team7/inplace/admin/crawling/client/KakaoMapClient.java
@@ -1,7 +1,6 @@
-package team7.inplace.crawling.client;
+package team7.inplace.admin.crawling.client;
import com.fasterxml.jackson.databind.JsonNode;
-import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
@@ -10,11 +9,13 @@
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
-import team7.inplace.crawling.client.dto.PlaceNode;
+import team7.inplace.admin.crawling.client.dto.PlaceNode;
import team7.inplace.global.exception.InplaceException;
import team7.inplace.global.exception.code.PlaceErrorCode;
import team7.inplace.global.kakao.config.KakaoApiProperties;
+import java.util.Objects;
+
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/src/main/java/team7/inplace/crawling/client/YoutubeClient.java b/src/main/java/team7/inplace/admin/crawling/client/YoutubeClient.java
similarity index 98%
rename from src/main/java/team7/inplace/crawling/client/YoutubeClient.java
rename to src/main/java/team7/inplace/admin/crawling/client/YoutubeClient.java
index 71784077..152e4bb8 100644
--- a/src/main/java/team7/inplace/crawling/client/YoutubeClient.java
+++ b/src/main/java/team7/inplace/admin/crawling/client/YoutubeClient.java
@@ -1,14 +1,15 @@
-package team7.inplace.crawling.client;
+package team7.inplace.admin.crawling.client;
import com.fasterxml.jackson.databind.JsonNode;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
@Slf4j
@Component
public class YoutubeClient {
diff --git a/src/main/java/team7/inplace/crawling/client/dto/PlaceNode.java b/src/main/java/team7/inplace/admin/crawling/client/dto/PlaceNode.java
similarity index 84%
rename from src/main/java/team7/inplace/crawling/client/dto/PlaceNode.java
rename to src/main/java/team7/inplace/admin/crawling/client/dto/PlaceNode.java
index 4f919cc3..397cb8dc 100644
--- a/src/main/java/team7/inplace/crawling/client/dto/PlaceNode.java
+++ b/src/main/java/team7/inplace/admin/crawling/client/dto/PlaceNode.java
@@ -1,4 +1,4 @@
-package team7.inplace.crawling.client.dto;
+package team7.inplace.admin.crawling.client.dto;
import com.fasterxml.jackson.databind.JsonNode;
diff --git a/src/main/java/team7/inplace/crawling/domain/ChannelType.java b/src/main/java/team7/inplace/admin/crawling/domain/ChannelType.java
similarity index 81%
rename from src/main/java/team7/inplace/crawling/domain/ChannelType.java
rename to src/main/java/team7/inplace/admin/crawling/domain/ChannelType.java
index 1a4b47b1..5733cec5 100644
--- a/src/main/java/team7/inplace/crawling/domain/ChannelType.java
+++ b/src/main/java/team7/inplace/admin/crawling/domain/ChannelType.java
@@ -1,4 +1,4 @@
-package team7.inplace.crawling.domain;
+package team7.inplace.admin.crawling.domain;
public enum ChannelType {
FOOD("FD6");
diff --git a/src/main/java/team7/inplace/crawling/domain/YoutubeChannel.java b/src/main/java/team7/inplace/admin/crawling/domain/YoutubeChannel.java
similarity index 76%
rename from src/main/java/team7/inplace/crawling/domain/YoutubeChannel.java
rename to src/main/java/team7/inplace/admin/crawling/domain/YoutubeChannel.java
index 1fadeab8..1b023d47 100644
--- a/src/main/java/team7/inplace/crawling/domain/YoutubeChannel.java
+++ b/src/main/java/team7/inplace/admin/crawling/domain/YoutubeChannel.java
@@ -1,16 +1,12 @@
-package team7.inplace.crawling.domain;
+package team7.inplace.admin.crawling.domain;
-import static jakarta.persistence.EnumType.STRING;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
+import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import static jakarta.persistence.EnumType.STRING;
+
@Getter
@Entity(name = "youtube_channel")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
diff --git a/src/main/java/team7/inplace/crawling/persistence/YoutubeChannelRepository.java b/src/main/java/team7/inplace/admin/crawling/persistence/YoutubeChannelRepository.java
similarity index 70%
rename from src/main/java/team7/inplace/crawling/persistence/YoutubeChannelRepository.java
rename to src/main/java/team7/inplace/admin/crawling/persistence/YoutubeChannelRepository.java
index 3c5146fa..b015a0cb 100644
--- a/src/main/java/team7/inplace/crawling/persistence/YoutubeChannelRepository.java
+++ b/src/main/java/team7/inplace/admin/crawling/persistence/YoutubeChannelRepository.java
@@ -1,8 +1,9 @@
-package team7.inplace.crawling.persistence;
+package team7.inplace.admin.crawling.persistence;
-import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
-import team7.inplace.crawling.domain.YoutubeChannel;
+import team7.inplace.admin.crawling.domain.YoutubeChannel;
+
+import java.util.Optional;
public interface YoutubeChannelRepository extends JpaRepository {
Optional findYoutubeChannelByPlayListUUID(String playListUUID);
diff --git a/src/main/java/team7/inplace/crawling/presentation/CrawlingController.java b/src/main/java/team7/inplace/admin/crawling/presentation/CrawlingController.java
similarity index 61%
rename from src/main/java/team7/inplace/crawling/presentation/CrawlingController.java
rename to src/main/java/team7/inplace/admin/crawling/presentation/CrawlingController.java
index 09c111bb..f616b992 100644
--- a/src/main/java/team7/inplace/crawling/presentation/CrawlingController.java
+++ b/src/main/java/team7/inplace/admin/crawling/presentation/CrawlingController.java
@@ -1,4 +1,4 @@
-package team7.inplace.crawling.presentation;
+package team7.inplace.admin.crawling.presentation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
@@ -7,7 +7,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import team7.inplace.crawling.application.CrawlingFacade;
+import team7.inplace.admin.crawling.application.CrawlingFacade;
@RestController
@RequestMapping("/crawling")
@@ -21,4 +21,18 @@ public ResponseEntity addPlaceInfo(@PathVariable Long videoId, @PathVariab
return ResponseEntity.status(HttpStatus.CREATED).build();
}
+
+ @PostMapping("/video")
+ public ResponseEntity crawlingVideo() {
+ crawlingFacade.updateVideos();
+
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
+
+ @PostMapping("/video/view")
+ public ResponseEntity crawlingVideoView() {
+ crawlingFacade.updateVideoView();
+
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ }
}
diff --git a/src/main/java/team7/inplace/global/exception/ErrorLog.java b/src/main/java/team7/inplace/admin/error/ErrorLog.java
similarity index 79%
rename from src/main/java/team7/inplace/global/exception/ErrorLog.java
rename to src/main/java/team7/inplace/admin/error/ErrorLog.java
index 4c909807..28cef11b 100644
--- a/src/main/java/team7/inplace/global/exception/ErrorLog.java
+++ b/src/main/java/team7/inplace/admin/error/ErrorLog.java
@@ -1,10 +1,6 @@
-package team7.inplace.global.exception;
+package team7.inplace.admin.error;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
+import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/src/main/java/team7/inplace/admin/ErrorLogController.java b/src/main/java/team7/inplace/admin/error/ErrorLogController.java
similarity index 89%
rename from src/main/java/team7/inplace/admin/ErrorLogController.java
rename to src/main/java/team7/inplace/admin/error/ErrorLogController.java
index e73bdfb3..c093537f 100644
--- a/src/main/java/team7/inplace/admin/ErrorLogController.java
+++ b/src/main/java/team7/inplace/admin/error/ErrorLogController.java
@@ -1,4 +1,4 @@
-package team7.inplace.admin;
+package team7.inplace.admin.error;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
@@ -6,7 +6,6 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import team7.inplace.global.exception.ErrorLogRepository;
@RestController
@RequiredArgsConstructor
@@ -20,7 +19,7 @@ public ResponseEntity resolveErrorLog(@PathVariable Long id) {
errorLog.resolve();
errorLogRepository.save(errorLog);
});
-
+
return ResponseEntity.ok().build();
}
}
diff --git a/src/main/java/team7/inplace/global/exception/ErrorLogRepository.java b/src/main/java/team7/inplace/admin/error/ErrorLogRepository.java
similarity index 83%
rename from src/main/java/team7/inplace/global/exception/ErrorLogRepository.java
rename to src/main/java/team7/inplace/admin/error/ErrorLogRepository.java
index c25d0820..163ddff0 100644
--- a/src/main/java/team7/inplace/global/exception/ErrorLogRepository.java
+++ b/src/main/java/team7/inplace/admin/error/ErrorLogRepository.java
@@ -1,8 +1,9 @@
-package team7.inplace.global.exception;
+package team7.inplace.admin.error;
-import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
+
public interface ErrorLogRepository extends JpaRepository {
List findByIsResolvedFalse();
}
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/application/FavoriteInfluencerService.java b/src/main/java/team7/inplace/favoriteInfluencer/application/FavoriteInfluencerService.java
index 46ab48e1..0b37fb99 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/application/FavoriteInfluencerService.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/application/FavoriteInfluencerService.java
@@ -1,7 +1,8 @@
package team7.inplace.favoriteInfluencer.application;
-import java.util.List;
import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import team7.inplace.favoriteInfluencer.application.dto.FavoriteInfluencerCommand;
@@ -10,12 +11,14 @@
import team7.inplace.favoriteInfluencer.persistent.FavoriteInfluencerRepository;
import team7.inplace.global.exception.InplaceException;
import team7.inplace.global.exception.code.AuthorizationErrorCode;
-import team7.inplace.global.exception.code.UserErrorCode;
+import team7.inplace.influencer.application.dto.InfluencerInfo;
import team7.inplace.influencer.domain.Influencer;
import team7.inplace.influencer.persistence.InfluencerRepository;
+import team7.inplace.security.application.CurrentUserProvider;
import team7.inplace.security.util.AuthorizationUtil;
import team7.inplace.user.domain.User;
-import team7.inplace.user.persistence.UserRepository;
+
+import java.util.List;
@RequiredArgsConstructor
@Service
@@ -23,7 +26,7 @@ public class FavoriteInfluencerService {
private final InfluencerRepository influencerRepository;
private final FavoriteInfluencerRepository favoriteRepository;
- private final UserRepository userRepository;
+ private final CurrentUserProvider currentUserProvider;
@Transactional
public void likeToInfluencer(FavoriteInfluencerCommand command) {
@@ -31,17 +34,10 @@ public void likeToInfluencer(FavoriteInfluencerCommand command) {
throw InplaceException.of(AuthorizationErrorCode.TOKEN_IS_EMPTY);
}
- Long userId = AuthorizationUtil.getUserId();
- User user = userRepository.findById(userId)
- .orElseThrow(() -> InplaceException.of(UserErrorCode.NOT_FOUND));
+ User user = currentUserProvider.getCurrentUser();
Influencer influencer = influencerRepository.findById(command.influencerId()).orElseThrow();
- FavoriteInfluencer favorite = favoriteRepository.findByUserIdAndInfluencerId(userId,
- influencer.getId())
- .orElseGet(() -> new FavoriteInfluencer(user, influencer)); // 존재하지 않으면 새로 생성
-
- favorite.updateLike(command.likes());
- favoriteRepository.save(favorite);
+ processFavoriteInfluencer(user, influencer, command.likes());
}
@Transactional
@@ -50,20 +46,32 @@ public void likeToManyInfluencer(FavoriteInfluencerListCommand command) {
throw InplaceException.of(AuthorizationErrorCode.TOKEN_IS_EMPTY);
}
- Long userId = AuthorizationUtil.getUserId();
- User user = userRepository.findById(userId)
- .orElseThrow(() -> InplaceException.of(UserErrorCode.NOT_FOUND));
-
+ User user = currentUserProvider.getCurrentUser();
List influencers = influencerRepository.findAllById(command.influencerIds());
for (Influencer influencer : influencers) {
+ processFavoriteInfluencer(user, influencer, command.likes());
+ }
+ }
- FavoriteInfluencer favorite = favoriteRepository.findByUserIdAndInfluencerId(userId,
- influencer.getId())
+ private void processFavoriteInfluencer(User user, Influencer influencer, Boolean likes) {
+ FavoriteInfluencer favorite = favoriteRepository
+ .findByUserIdAndInfluencerId(user.getId(), influencer.getId())
.orElseGet(() -> new FavoriteInfluencer(user, influencer)); // 존재하지 않으면 새로 생성
- favorite.updateLike(command.likes());
+ favorite.updateLike(likes);
+ if (favorite.getId() == null) {
favoriteRepository.save(favorite);
}
}
+
+ @Transactional(readOnly = true)
+ public Page getFavoriteInfluencers(Long userId, Pageable pageable) {
+ Page influencerPage = favoriteRepository.findByUserIdAndIsLikedTrue(
+ userId, pageable);
+
+ return influencerPage.map(
+ favorite -> InfluencerInfo.from(favorite.getInfluencer(), favorite.isLiked()));
+ }
}
+
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerCommand.java b/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerCommand.java
index e9f4bc8f..f2f318c0 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerCommand.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerCommand.java
@@ -1,8 +1,8 @@
package team7.inplace.favoriteInfluencer.application.dto;
public record FavoriteInfluencerCommand(
- Long influencerId,
- Boolean likes
+ Long influencerId,
+ Boolean likes
) {
}
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerListCommand.java b/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerListCommand.java
index ebf7dd67..3a167412 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerListCommand.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/application/dto/FavoriteInfluencerListCommand.java
@@ -3,8 +3,8 @@
import java.util.List;
public record FavoriteInfluencerListCommand(
- List influencerIds,
- Boolean likes
+ List influencerIds,
+ Boolean likes
) {
}
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/domain/FavoriteInfluencer.java b/src/main/java/team7/inplace/favoriteInfluencer/domain/FavoriteInfluencer.java
index 5d0fa4f2..0b8612f9 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/domain/FavoriteInfluencer.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/domain/FavoriteInfluencer.java
@@ -1,14 +1,6 @@
package team7.inplace.favoriteInfluencer.domain;
-import static jakarta.persistence.GenerationType.IDENTITY;
-import static lombok.AccessLevel.PROTECTED;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
+import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@@ -16,6 +8,9 @@
import team7.inplace.influencer.domain.Influencer;
import team7.inplace.user.domain.User;
+import static jakarta.persistence.GenerationType.IDENTITY;
+import static lombok.AccessLevel.PROTECTED;
+
@Getter
@RequiredArgsConstructor
@NoArgsConstructor(access = PROTECTED)
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/persistent/FavoriteInfluencerRepository.java b/src/main/java/team7/inplace/favoriteInfluencer/persistent/FavoriteInfluencerRepository.java
index e850f167..ead22138 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/persistent/FavoriteInfluencerRepository.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/persistent/FavoriteInfluencerRepository.java
@@ -1,17 +1,22 @@
package team7.inplace.favoriteInfluencer.persistent;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import team7.inplace.favoriteInfluencer.domain.FavoriteInfluencer;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
public interface FavoriteInfluencerRepository extends JpaRepository {
List findByUserId(Long userId);
+ Page findByUserIdAndIsLikedTrue(Long userId, Pageable pageable);
+
Optional findByUserIdAndInfluencerId(Long userId, Long influencerId);
@Query("SELECT f.influencer.id FROM FavoriteInfluencer f WHERE f.user.id = :userId AND f.isLiked = true")
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/presentation/FavoriteInfluencerController.java b/src/main/java/team7/inplace/favoriteInfluencer/presentation/FavoriteInfluencerController.java
index f60c8576..72baebb8 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/presentation/FavoriteInfluencerController.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/presentation/FavoriteInfluencerController.java
@@ -29,7 +29,7 @@ public ResponseEntity likeToInfluencer(@RequestBody InfluencerLikeRequest
@PostMapping("/multiple/likes")
public ResponseEntity likeToManyInfluencer(
- @RequestBody InfluencerListLikeRequest request) {
+ @RequestBody InfluencerListLikeRequest request) {
FavoriteInfluencerListCommand command = request.toCommand();
favoriteInfluencerService.likeToManyInfluencer(command);
return new ResponseEntity<>(HttpStatus.OK);
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerLikeRequest.java b/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerLikeRequest.java
index 0ee70a78..5c2f9fc1 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerLikeRequest.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerLikeRequest.java
@@ -3,8 +3,8 @@
import team7.inplace.favoriteInfluencer.application.dto.FavoriteInfluencerCommand;
public record InfluencerLikeRequest(
- Long influencerId,
- Boolean likes
+ Long influencerId,
+ Boolean likes
) {
public FavoriteInfluencerCommand toCommand() {
diff --git a/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerListLikeRequest.java b/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerListLikeRequest.java
index 6f5fa80a..ec4406f2 100644
--- a/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerListLikeRequest.java
+++ b/src/main/java/team7/inplace/favoriteInfluencer/presentation/dto/InfluencerListLikeRequest.java
@@ -1,11 +1,12 @@
package team7.inplace.favoriteInfluencer.presentation.dto;
-import java.util.List;
import team7.inplace.favoriteInfluencer.application.dto.FavoriteInfluencerListCommand;
+import java.util.List;
+
public record InfluencerListLikeRequest(
- List influencerIds,
- Boolean likes
+ List influencerIds,
+ Boolean likes
) {
public FavoriteInfluencerListCommand toCommand() {
diff --git a/src/main/java/team7/inplace/global/annotation/Facade.java b/src/main/java/team7/inplace/global/annotation/Facade.java
index 1b0f2071..70fe10e2 100644
--- a/src/main/java/team7/inplace/global/annotation/Facade.java
+++ b/src/main/java/team7/inplace/global/annotation/Facade.java
@@ -1,13 +1,10 @@
package team7.inplace.global.annotation;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
+import java.lang.annotation.*;
+
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
diff --git a/src/main/java/team7/inplace/global/exception/InplaceExceptionHandler.java b/src/main/java/team7/inplace/global/exception/InplaceExceptionHandler.java
index dd7426a6..49219fea 100644
--- a/src/main/java/team7/inplace/global/exception/InplaceExceptionHandler.java
+++ b/src/main/java/team7/inplace/global/exception/InplaceExceptionHandler.java
@@ -1,7 +1,6 @@
package team7.inplace.global.exception;
import jakarta.servlet.http.HttpServletRequest;
-import java.net.URI;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -9,6 +8,10 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
+import team7.inplace.admin.error.ErrorLog;
+import team7.inplace.admin.error.ErrorLogRepository;
+
+import java.net.URI;
@Slf4j
@RestControllerAdvice
diff --git a/src/main/java/team7/inplace/global/exception/code/AuthorizationErrorCode.java b/src/main/java/team7/inplace/global/exception/code/AuthorizationErrorCode.java
index 640aaccf..f4502a93 100644
--- a/src/main/java/team7/inplace/global/exception/code/AuthorizationErrorCode.java
+++ b/src/main/java/team7/inplace/global/exception/code/AuthorizationErrorCode.java
@@ -7,9 +7,10 @@
@AllArgsConstructor
@Getter
public enum AuthorizationErrorCode implements ErrorCode {
- TOKEN_IS_EMPTY(HttpStatus.BAD_REQUEST, "A001", "Token is Empty"),
- INVALID_TOKEN(HttpStatus.BAD_REQUEST, "A002", "Invalid Token"),
- TOKEN_IS_EXPIRED(HttpStatus.BAD_REQUEST, "A003", "Token is Expired");
+ NOT_AUTHENTICATION(HttpStatus.UNAUTHORIZED, "A000", "Authentication failed"),
+ TOKEN_IS_EMPTY(HttpStatus.BAD_REQUEST, "A001", "not include token"),
+ INVALID_TOKEN(HttpStatus.BAD_REQUEST, "A002", "Invalid token"),
+ TOKEN_IS_EXPIRED(HttpStatus.BAD_REQUEST, "A003", "token is expired");
private final HttpStatus httpStatus;
private final String errorCode;
diff --git a/src/main/java/team7/inplace/global/exception/code/BannerErrorCode.java b/src/main/java/team7/inplace/global/exception/code/BannerErrorCode.java
new file mode 100644
index 00000000..20809c4c
--- /dev/null
+++ b/src/main/java/team7/inplace/global/exception/code/BannerErrorCode.java
@@ -0,0 +1,32 @@
+package team7.inplace.global.exception.code;
+
+import org.springframework.http.HttpStatus;
+
+public enum BannerErrorCode implements ErrorCode {
+ NOT_FOUND("B001", "배너를 찾을 수 없습니다.", HttpStatus.NOT_FOUND);
+
+ private final String code;
+ private final String message;
+ private final HttpStatus httpStatus;
+
+ BannerErrorCode(String code, String message, HttpStatus httpStatus) {
+ this.code = code;
+ this.message = message;
+ this.httpStatus = httpStatus;
+ }
+
+ @Override
+ public HttpStatus httpStatus() {
+ return httpStatus;
+ }
+
+ @Override
+ public String code() {
+ return code;
+ }
+
+ @Override
+ public String message() {
+ return message;
+ }
+}
diff --git a/src/main/java/team7/inplace/global/exception/code/VideoErrorCode.java b/src/main/java/team7/inplace/global/exception/code/VideoErrorCode.java
index a1c6ea37..855ff964 100644
--- a/src/main/java/team7/inplace/global/exception/code/VideoErrorCode.java
+++ b/src/main/java/team7/inplace/global/exception/code/VideoErrorCode.java
@@ -6,7 +6,7 @@
@AllArgsConstructor
@Getter
-public enum VideoErrorCode implements ErrorCode{
+public enum VideoErrorCode implements ErrorCode {
NOT_FOUND(HttpStatus.NOT_FOUND, "V001", "Can't find such video info");
private final HttpStatus httpStatus;
diff --git a/src/main/java/team7/inplace/global/kakao/config/KakaoApiProperties.java b/src/main/java/team7/inplace/global/kakao/config/KakaoApiProperties.java
index c725c472..32285510 100644
--- a/src/main/java/team7/inplace/global/kakao/config/KakaoApiProperties.java
+++ b/src/main/java/team7/inplace/global/kakao/config/KakaoApiProperties.java
@@ -4,8 +4,9 @@
@ConfigurationProperties(prefix = "kakao.api")
public record KakaoApiProperties(
- String key,
- String sendMessageToMeUrl
+ String key,
+ String jsKey,
+ String sendMessageToMeUrl
) {
public String getAuthorization() {
diff --git a/src/main/java/team7/inplace/global/rest/WebClientConfig.java b/src/main/java/team7/inplace/global/rest/WebClientConfig.java
index 404309ba..b968aaa5 100644
--- a/src/main/java/team7/inplace/global/rest/WebClientConfig.java
+++ b/src/main/java/team7/inplace/global/rest/WebClientConfig.java
@@ -2,7 +2,11 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
+import reactor.netty.http.client.HttpClient;
+
+import java.time.Duration;
@Configuration
public class WebClientConfig {
@@ -10,7 +14,10 @@ public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
- .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
- .build();
+ .clientConnector(new ReactorClientHttpConnector(
+ HttpClient.create().responseTimeout(Duration.ofMillis(60000))
+ ))
+ .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
+ .build();
}
}
diff --git a/src/main/java/team7/inplace/global/scheduler/ScheduledExecutorConfig.java b/src/main/java/team7/inplace/global/scheduler/ScheduledExecutorConfig.java
index afcc36c0..0c4f502e 100644
--- a/src/main/java/team7/inplace/global/scheduler/ScheduledExecutorConfig.java
+++ b/src/main/java/team7/inplace/global/scheduler/ScheduledExecutorConfig.java
@@ -4,8 +4,10 @@
import java.util.concurrent.ScheduledExecutorService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
+@EnableScheduling
public class ScheduledExecutorConfig {
@Bean
diff --git a/src/main/java/team7/inplace/influencer/application/InfluencerService.java b/src/main/java/team7/inplace/influencer/application/InfluencerService.java
index e8ec9cf9..c8a03cb4 100644
--- a/src/main/java/team7/inplace/influencer/application/InfluencerService.java
+++ b/src/main/java/team7/inplace/influencer/application/InfluencerService.java
@@ -1,7 +1,5 @@
package team7.inplace.influencer.application;
-import java.util.List;
-import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -17,6 +15,9 @@
import team7.inplace.security.util.AuthorizationUtil;
import team7.inplace.user.persistence.UserRepository;
+import java.util.List;
+import java.util.Set;
+
@RequiredArgsConstructor
@Service
public class InfluencerService {
@@ -39,12 +40,12 @@ public Page getAllInfluencers(Pageable pageable) {
Set likedInfluencerIds = favoriteRepository.findLikedInfluencerIdsByUserId(userId);
List influencerInfos = influencersPage.stream()
- .map(influencer -> {
- boolean isLiked = likedInfluencerIds.contains(influencer.getId());
- return InfluencerInfo.from(influencer, isLiked);
- })
- .sorted((a, b) -> Boolean.compare(b.likes(), a.likes()))
- .toList();
+ .map(influencer -> {
+ boolean isLiked = likedInfluencerIds.contains(influencer.getId());
+ return InfluencerInfo.from(influencer, isLiked);
+ })
+ .sorted((a, b) -> Boolean.compare(b.likes(), a.likes()))
+ .toList();
return new PageImpl<>(influencerInfos, pageable, influencersPage.getTotalElements());
}
@@ -53,8 +54,8 @@ public Page getAllInfluencers(Pageable pageable) {
public List getAllInfluencerNames() {
List names = influencerRepository.findAllInfluencerNames();
return names.stream()
- .map(InfluencerNameInfo::new)
- .toList();
+ .map(InfluencerNameInfo::new)
+ .toList();
}
@Transactional
@@ -67,7 +68,7 @@ public Long createInfluencer(InfluencerCommand command) {
public Long updateInfluencer(Long id, InfluencerCommand command) {
Influencer influencer = influencerRepository.findById(id).orElseThrow();
influencer.update(command.influencerName(), command.influencerImgUrl(),
- command.influencerJob());
+ command.influencerJob());
return influencer.getId();
}
diff --git a/src/main/java/team7/inplace/influencer/application/dto/InfluencerCommand.java b/src/main/java/team7/inplace/influencer/application/dto/InfluencerCommand.java
index c0ac6e03..99e0e9bd 100644
--- a/src/main/java/team7/inplace/influencer/application/dto/InfluencerCommand.java
+++ b/src/main/java/team7/inplace/influencer/application/dto/InfluencerCommand.java
@@ -3,16 +3,16 @@
import team7.inplace.influencer.domain.Influencer;
public record InfluencerCommand(
- String influencerName,
- String influencerImgUrl,
- String influencerJob
+ String influencerName,
+ String influencerImgUrl,
+ String influencerJob
) {
public static Influencer to(InfluencerCommand influencerCommand) {
return new Influencer(
- influencerCommand.influencerName,
- influencerCommand.influencerImgUrl,
- influencerCommand.influencerJob
+ influencerCommand.influencerName,
+ influencerCommand.influencerImgUrl,
+ influencerCommand.influencerJob
);
}
}
diff --git a/src/main/java/team7/inplace/influencer/application/dto/InfluencerInfo.java b/src/main/java/team7/inplace/influencer/application/dto/InfluencerInfo.java
index ca82957f..7f65194a 100644
--- a/src/main/java/team7/inplace/influencer/application/dto/InfluencerInfo.java
+++ b/src/main/java/team7/inplace/influencer/application/dto/InfluencerInfo.java
@@ -3,20 +3,20 @@
import team7.inplace.influencer.domain.Influencer;
public record InfluencerInfo(
- Long influencerId,
- String influencerName,
- String influencerImgUrl,
- String influencerJob,
- boolean likes
+ Long influencerId,
+ String influencerName,
+ String influencerImgUrl,
+ String influencerJob,
+ boolean likes
) {
public static InfluencerInfo from(Influencer influencer, boolean isLiked) {
return new InfluencerInfo(
- influencer.getId(),
- influencer.getName(),
- influencer.getImgUrl(),
- influencer.getJob(),
- isLiked
+ influencer.getId(),
+ influencer.getName(),
+ influencer.getImgUrl(),
+ influencer.getJob(),
+ isLiked
);
}
}
diff --git a/src/main/java/team7/inplace/influencer/domain/Influencer.java b/src/main/java/team7/inplace/influencer/domain/Influencer.java
index e35d1899..69154ac6 100644
--- a/src/main/java/team7/inplace/influencer/domain/Influencer.java
+++ b/src/main/java/team7/inplace/influencer/domain/Influencer.java
@@ -1,16 +1,12 @@
package team7.inplace.influencer.domain;
-import static lombok.AccessLevel.PROTECTED;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
+import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import static lombok.AccessLevel.PROTECTED;
+
@Getter
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor
diff --git a/src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java b/src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java
index 8a620738..40b4e5f5 100644
--- a/src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java
+++ b/src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java
@@ -1,12 +1,13 @@
package team7.inplace.influencer.persistence;
-import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import team7.inplace.influencer.domain.Influencer;
+import java.util.List;
+
public interface InfluencerRepository extends JpaRepository {
@Override
diff --git a/src/main/java/team7/inplace/influencer/presentation/InfluencerController.java b/src/main/java/team7/inplace/influencer/presentation/InfluencerController.java
index 23cacb42..f9738682 100644
--- a/src/main/java/team7/inplace/influencer/presentation/InfluencerController.java
+++ b/src/main/java/team7/inplace/influencer/presentation/InfluencerController.java
@@ -1,26 +1,20 @@
package team7.inplace.influencer.presentation;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import team7.inplace.influencer.application.InfluencerService;
import team7.inplace.influencer.application.dto.InfluencerCommand;
import team7.inplace.influencer.presentation.dto.InfluencerNameResponse;
import team7.inplace.influencer.presentation.dto.InfluencerRequest;
import team7.inplace.influencer.presentation.dto.InfluencerResponse;
+import java.util.List;
+
@RequiredArgsConstructor
@RestController
@RequestMapping("/influencers")
@@ -30,9 +24,9 @@ public class InfluencerController implements InfluencerControllerApiSpec {
@GetMapping()
public ResponseEntity> getAllInfluencers(
- @PageableDefault(page = 0, size = 10) Pageable pageable) {
+ @PageableDefault(page = 0, size = 10) Pageable pageable) {
Page influencers = influencerService.getAllInfluencers(pageable)
- .map(InfluencerResponse::from);
+ .map(InfluencerResponse::from);
return new ResponseEntity<>(influencers, HttpStatus.OK);
}
@@ -40,8 +34,8 @@ public ResponseEntity> getAllInfluencers(
@GetMapping("/names")
public ResponseEntity> getAllInfluencerNames() {
List names = influencerService.getAllInfluencerNames().stream()
- .map(InfluencerNameResponse::from)
- .toList();
+ .map(InfluencerNameResponse::from)
+ .toList();
return new ResponseEntity<>(names, HttpStatus.OK);
}
@@ -55,13 +49,13 @@ public ResponseEntity createInfluencer(@RequestBody InfluencerRequest requ
@PutMapping("/{id}")
public ResponseEntity updateInfluencer(
- @PathVariable Long id,
- @RequestBody InfluencerRequest request
+ @PathVariable Long id,
+ @RequestBody InfluencerRequest request
) {
InfluencerCommand influencerCommand = new InfluencerCommand(
- request.influencerName(),
- request.influencerImgUrl(),
- request.influencerJob()
+ request.influencerName(),
+ request.influencerImgUrl(),
+ request.influencerJob()
);
Long updatedId = influencerService.updateInfluencer(id, influencerCommand);
diff --git a/src/main/java/team7/inplace/influencer/presentation/InfluencerControllerApiSpec.java b/src/main/java/team7/inplace/influencer/presentation/InfluencerControllerApiSpec.java
index 520853b7..dccc9b7c 100644
--- a/src/main/java/team7/inplace/influencer/presentation/InfluencerControllerApiSpec.java
+++ b/src/main/java/team7/inplace/influencer/presentation/InfluencerControllerApiSpec.java
@@ -1,7 +1,6 @@
package team7.inplace.influencer.presentation;
import io.swagger.v3.oas.annotations.Operation;
-import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
@@ -11,6 +10,8 @@
import team7.inplace.influencer.presentation.dto.InfluencerRequest;
import team7.inplace.influencer.presentation.dto.InfluencerResponse;
+import java.util.List;
+
public interface InfluencerControllerApiSpec {
@Operation(summary = "인플루언서들 반환", description = "토큰이 있는 경우 좋아요된 인플루언서가 먼저 반환됩니다.")
@@ -24,7 +25,7 @@ public interface InfluencerControllerApiSpec {
@Operation(summary = "인플루언서 수정", description = "인플루언서를 수정합니다.")
ResponseEntity updateInfluencer(@PathVariable Long id,
- @RequestBody InfluencerRequest request);
+ @RequestBody InfluencerRequest request);
@Operation(summary = "인플루언서 삭제", description = "인플루언서를 삭제합니다.")
ResponseEntity deleteInfluencer(@PathVariable Long id);
diff --git a/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerNameResponse.java b/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerNameResponse.java
index e5c06660..fc821427 100644
--- a/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerNameResponse.java
+++ b/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerNameResponse.java
@@ -3,12 +3,12 @@
import team7.inplace.influencer.application.dto.InfluencerNameInfo;
public record InfluencerNameResponse(
- String influencerName
+ String influencerName
) {
public static InfluencerNameResponse from(InfluencerNameInfo influencerNameInfo) {
return new InfluencerNameResponse(
- influencerNameInfo.name()
+ influencerNameInfo.name()
);
}
}
diff --git a/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerRequest.java b/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerRequest.java
index a5ca7a26..2ac73abe 100644
--- a/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerRequest.java
+++ b/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerRequest.java
@@ -3,16 +3,16 @@
import team7.inplace.influencer.application.dto.InfluencerCommand;
public record InfluencerRequest(
- String influencerName,
- String influencerImgUrl,
- String influencerJob
+ String influencerName,
+ String influencerImgUrl,
+ String influencerJob
) {
public static InfluencerCommand to(InfluencerRequest request) {
return new InfluencerCommand(
- request.influencerName(),
- request.influencerImgUrl(),
- request.influencerJob()
+ request.influencerName(),
+ request.influencerImgUrl(),
+ request.influencerJob()
);
}
}
diff --git a/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerResponse.java b/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerResponse.java
index 12729aad..a2eb53a2 100644
--- a/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerResponse.java
+++ b/src/main/java/team7/inplace/influencer/presentation/dto/InfluencerResponse.java
@@ -3,20 +3,20 @@
import team7.inplace.influencer.application.dto.InfluencerInfo;
public record InfluencerResponse(
- Long influencerId,
- String influencerName,
- String influencerImgUrl,
- String influencerJob,
- boolean likes
+ Long influencerId,
+ String influencerName,
+ String influencerImgUrl,
+ String influencerJob,
+ boolean likes
) {
public static InfluencerResponse from(InfluencerInfo influencerInfo) {
return new InfluencerResponse(
- influencerInfo.influencerId(),
- influencerInfo.influencerName(),
- influencerInfo.influencerImgUrl(),
- influencerInfo.influencerJob(),
- influencerInfo.likes()
+ influencerInfo.influencerId(),
+ influencerInfo.influencerName(),
+ influencerInfo.influencerImgUrl(),
+ influencerInfo.influencerJob(),
+ influencerInfo.likes()
);
}
}
diff --git a/src/main/java/team7/inplace/infra/s3/AwsProperties.java b/src/main/java/team7/inplace/infra/s3/AwsProperties.java
new file mode 100644
index 00000000..4b470cad
--- /dev/null
+++ b/src/main/java/team7/inplace/infra/s3/AwsProperties.java
@@ -0,0 +1,12 @@
+package team7.inplace.infra.s3;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "aws")
+public record AwsProperties(
+ String accessKey,
+ String secretKey,
+ String region,
+ String bucketName
+) {
+}
diff --git a/src/main/java/team7/inplace/infra/s3/S3Config.java b/src/main/java/team7/inplace/infra/s3/S3Config.java
new file mode 100644
index 00000000..89ed79b3
--- /dev/null
+++ b/src/main/java/team7/inplace/infra/s3/S3Config.java
@@ -0,0 +1,26 @@
+package team7.inplace.infra.s3;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+public class S3Config {
+ private final AwsProperties awsProperties;
+
+ @Bean
+ public AmazonS3Client amazonS3Client() {
+ BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsProperties.accessKey(),
+ awsProperties.secretKey());
+
+ return (AmazonS3Client) AmazonS3ClientBuilder.standard()
+ .withRegion(awsProperties.region())
+ .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
+ .build();
+ }
+}
diff --git a/src/main/java/team7/inplace/likedPlace/domain/LikedPlace.java b/src/main/java/team7/inplace/likedPlace/domain/LikedPlace.java
index 35cdab35..ed31817c 100644
--- a/src/main/java/team7/inplace/likedPlace/domain/LikedPlace.java
+++ b/src/main/java/team7/inplace/likedPlace/domain/LikedPlace.java
@@ -1,15 +1,6 @@
package team7.inplace.likedPlace.domain;
-import static jakarta.persistence.GenerationType.IDENTITY;
-import static lombok.AccessLevel.PROTECTED;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@@ -17,6 +8,9 @@
import team7.inplace.place.domain.Place;
import team7.inplace.user.domain.User;
+import static jakarta.persistence.GenerationType.IDENTITY;
+import static lombok.AccessLevel.PROTECTED;
+
@Getter
@RequiredArgsConstructor
@NoArgsConstructor(access = PROTECTED)
diff --git a/src/main/java/team7/inplace/likedPlace/persistence/LikedPlaceRepository.java b/src/main/java/team7/inplace/likedPlace/persistence/LikedPlaceRepository.java
index ed63160b..0ae36e7f 100644
--- a/src/main/java/team7/inplace/likedPlace/persistence/LikedPlaceRepository.java
+++ b/src/main/java/team7/inplace/likedPlace/persistence/LikedPlaceRepository.java
@@ -1,10 +1,23 @@
package team7.inplace.likedPlace.persistence;
import java.util.Optional;
+import java.util.Set;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import team7.inplace.likedPlace.domain.LikedPlace;
public interface LikedPlaceRepository extends JpaRepository {
Optional findByUserIdAndPlaceId(Long userId, Long placeId);
+
+ @Query("SELECT l.place.id FROM LikedPlace l WHERE l.user.id = :userId AND l.isLiked = true")
+ Set findPlaceIdsByUserIdAndIsLikedTrue(@Param("userId") Long userId);
+
+ @Query("SELECT lp FROM LikedPlace lp JOIN FETCH lp.place WHERE lp.user.id = :userId AND lp.isLiked = true")
+ Page findByUserIdAndIsLikedTrueWithPlace(@Param("userId") Long userId,
+ Pageable pageable);
+
}
diff --git a/src/main/java/team7/inplace/oauthToken/application/OauthTokenService.java b/src/main/java/team7/inplace/oauthToken/application/OauthTokenService.java
index aec4df0b..5a031c4f 100644
--- a/src/main/java/team7/inplace/oauthToken/application/OauthTokenService.java
+++ b/src/main/java/team7/inplace/oauthToken/application/OauthTokenService.java
@@ -23,17 +23,17 @@ public class OauthTokenService {
@Transactional(readOnly = true)
public String findOAuthTokenByUserId(Long userId) throws InplaceException {
return tokenEncryptionUtil.decrypt(oauthTokenRepository.findByUserId(userId)
- .orElseThrow(() -> InplaceException.of(UserErrorCode.OAUTH_TOKEN_NOT_FOUND))
- .getOauthToken());
+ .orElseThrow(() -> InplaceException.of(UserErrorCode.OAUTH_TOKEN_NOT_FOUND))
+ .getOauthToken());
}
@Transactional
public void insertOauthToken(OauthTokenCommand oauthTokenCommand) throws InplaceException {
User userProxy = entityManager.getReference(User.class, oauthTokenCommand.userId());
OauthToken oauthToken = OauthToken.of(
- tokenEncryptionUtil.encrypt(oauthTokenCommand.oauthToken()),
- oauthTokenCommand.expiresAt(),
- userProxy
+ tokenEncryptionUtil.encrypt(oauthTokenCommand.oauthToken()),
+ oauthTokenCommand.expiresAt(),
+ userProxy
);
oauthTokenRepository.save(oauthToken);
@@ -42,11 +42,11 @@ public void insertOauthToken(OauthTokenCommand oauthTokenCommand) throws Inplace
@Transactional
public void updateOauthToken(OauthTokenCommand oauthTokenCommand) throws InplaceException {
OauthToken oauthToken = oauthTokenRepository.findByUserId(oauthTokenCommand.userId())
- .orElseThrow(() -> InplaceException.of(UserErrorCode.OAUTH_TOKEN_NOT_FOUND));
+ .orElseThrow(() -> InplaceException.of(UserErrorCode.OAUTH_TOKEN_NOT_FOUND));
oauthToken.updateInfo(
- tokenEncryptionUtil.encrypt(oauthTokenCommand.oauthToken()),
- oauthTokenCommand.expiresAt()
+ tokenEncryptionUtil.encrypt(oauthTokenCommand.oauthToken()),
+ oauthTokenCommand.expiresAt()
);
}
diff --git a/src/main/java/team7/inplace/oauthToken/application/command/OauthTokenCommand.java b/src/main/java/team7/inplace/oauthToken/application/command/OauthTokenCommand.java
index c8b53f54..5b1aa3e9 100644
--- a/src/main/java/team7/inplace/oauthToken/application/command/OauthTokenCommand.java
+++ b/src/main/java/team7/inplace/oauthToken/application/command/OauthTokenCommand.java
@@ -3,9 +3,9 @@
import java.time.Instant;
public record OauthTokenCommand(
- String oauthToken,
- Instant expiresAt,
- Long userId
+ String oauthToken,
+ Instant expiresAt,
+ Long userId
) {
public static OauthTokenCommand of(String oauthToken, Instant expiresAt, Long userId) {
diff --git a/src/main/java/team7/inplace/oauthToken/domain/OauthToken.java b/src/main/java/team7/inplace/oauthToken/domain/OauthToken.java
index cc9c77a5..8e4eadfb 100644
--- a/src/main/java/team7/inplace/oauthToken/domain/OauthToken.java
+++ b/src/main/java/team7/inplace/oauthToken/domain/OauthToken.java
@@ -1,20 +1,15 @@
package team7.inplace.oauthToken.domain;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.OneToOne;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
+import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import team7.inplace.user.domain.User;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
diff --git a/src/main/java/team7/inplace/oauthToken/persistence/OauthTokenRepository.java b/src/main/java/team7/inplace/oauthToken/persistence/OauthTokenRepository.java
index 4bb2c499..bf09ccd5 100644
--- a/src/main/java/team7/inplace/oauthToken/persistence/OauthTokenRepository.java
+++ b/src/main/java/team7/inplace/oauthToken/persistence/OauthTokenRepository.java
@@ -1,10 +1,11 @@
package team7.inplace.oauthToken.persistence;
-import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import team7.inplace.oauthToken.domain.OauthToken;
+import java.util.Optional;
+
@Repository
public interface OauthTokenRepository extends JpaRepository {
diff --git a/src/main/java/team7/inplace/place/application/CategoryService.java b/src/main/java/team7/inplace/place/application/CategoryService.java
index e3103751..f8ad2ff6 100644
--- a/src/main/java/team7/inplace/place/application/CategoryService.java
+++ b/src/main/java/team7/inplace/place/application/CategoryService.java
@@ -1,13 +1,14 @@
package team7.inplace.place.application;
-import java.util.Arrays;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import team7.inplace.place.application.dto.CategoryInfo;
import team7.inplace.place.domain.Category;
import team7.inplace.place.persistence.PlaceRepository;
+import java.util.Arrays;
+import java.util.List;
+
@Service
@RequiredArgsConstructor
public class CategoryService {
@@ -16,6 +17,6 @@ public class CategoryService {
public List getCategories() {
return Arrays.stream(Category.values()).map(category -> new CategoryInfo(category.name()))
- .toList();
+ .toList();
}
}
diff --git a/src/main/java/team7/inplace/place/application/PlaceService.java b/src/main/java/team7/inplace/place/application/PlaceService.java
index a1b1144f..0fb0161f 100644
--- a/src/main/java/team7/inplace/place/application/PlaceService.java
+++ b/src/main/java/team7/inplace/place/application/PlaceService.java
@@ -9,6 +9,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import team7.inplace.global.exception.InplaceException;
import team7.inplace.global.exception.code.AuthorizationErrorCode;
@@ -21,11 +22,14 @@
import team7.inplace.place.application.command.PlacesCommand.Create;
import team7.inplace.place.application.command.PlacesCommand.PlacesCoordinateCommand;
import team7.inplace.place.application.command.PlacesCommand.PlacesFilterParamsCommand;
+import team7.inplace.place.application.dto.LikedPlaceInfo;
import team7.inplace.place.application.dto.PlaceDetailInfo;
import team7.inplace.place.application.dto.PlaceInfo;
+import team7.inplace.place.domain.Category;
import team7.inplace.place.domain.Place;
import team7.inplace.place.persistence.PlaceRepository;
import team7.inplace.placeMessage.application.command.PlaceMessageCommand;
+import team7.inplace.review.persistence.ReviewRepository;
import team7.inplace.security.util.AuthorizationUtil;
import team7.inplace.user.domain.User;
import team7.inplace.user.persistence.UserRepository;
@@ -44,13 +48,18 @@ public class PlaceService {
private final LikedPlaceRepository likedPlaceRepository;
+ private final ReviewRepository reviewRepository;
+
public Page getPlacesWithinRadius(
PlacesCoordinateCommand placesCoordinateCommand,
PlacesFilterParamsCommand placesFilterParamsCommand) {
// categories와 influencers 필터 처리
List categoryFilters = placesFilterParamsCommand.isCategoryFilterExists()
- ? Arrays.stream(placesFilterParamsCommand.categories().split(",")).toList()
+ ? Arrays.stream(placesFilterParamsCommand.categories().split(","))
+ .map(Category::of)
+ .map(Category::name)
+ .toList()
: null;
List influencerFilters = placesFilterParamsCommand.isInfluencerFilterExists()
@@ -73,7 +82,7 @@ public Page getPlacesWithinRadius(
List placeInfos = convertToPlaceInfos(placesPage, placeIdToInfluencerName);
// PlaceInfo 리스트를 Page로 변환하여 반환
- return new PageImpl<>(placeInfos, placesPage.getPageable(), placeInfos.size());
+ return new PageImpl<>(placeInfos, placesPage.getPageable(), placesPage.getTotalElements());
}
private List convertToPlaceInfos(Page placesPage,
@@ -131,7 +140,11 @@ public PlaceDetailInfo getPlaceDetailInfo(Long placeId) {
video = videos.get(0);
}
Influencer influencer = (video != null) ? video.getInfluencer() : null;
- return PlaceDetailInfo.from(place, influencer, video, isLikedPlace(place.getId()));
+
+ Integer numOfLikes = reviewRepository.countByPlaceIdAndIsLikedTrue(placeId);
+ Integer numOfDislikes = reviewRepository.countByPlaceIdAndIsLikedFalse(placeId);
+ return PlaceDetailInfo.from(place, influencer, video, isLikedPlace(place.getId()),
+ numOfLikes, numOfDislikes);
}
public List createPlaces(List placeCommands) {
@@ -214,4 +227,22 @@ public PlaceMessageCommand getPlaceMessageCommand(Long placeId) {
return PlaceMessageCommand.of(place, influencer, video);
}
+
+ public Page getLikedPlaceInfo(Long userId, Pageable pageable) {
+ Page placePage = likedPlaceRepository.findByUserIdAndIsLikedTrueWithPlace(
+ userId, pageable);
+ List placeIds = placePage.map(likedPlace -> likedPlace.getPlace().getId()).toList();
+ List