diff --git a/.github/workflows/uckgisagi-deploy.yml b/.github/workflows/uckgisagi-deploy.yml
new file mode 100644
index 00000000..038f524b
--- /dev/null
+++ b/.github/workflows/uckgisagi-deploy.yml
@@ -0,0 +1,76 @@
+name: CI
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ "develop" ]
+
+env:
+ S3_BUCKET_NAME: uckgisagi-bucket
+ PROJECT_NAME: uckgisagi-server
+ RESOURCE_AWS_PATH: ./src/main/resources/application-aws.yml
+ RESOURCE_JWT_PATH: ./src/main/resources/application-jwt.yml
+ RESOURCE_PROD_PATH: ./src/main/resources/application-prod.yml
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Set up JDK 11
+ uses: actions/setup-java@v1
+ with:
+ java-version: '11'
+
+ - name: Set PROD Yml
+ uses: microsoft/variable-substitution@v1
+ with:
+ files: ${{ env.RESOURCE_PROD_PATH }}
+ env:
+ spring.datasource.url: ${{ secrets.RDS_URL }}
+ spring.datasource.username: ${{ secrets.RDS_USERNAME }}
+ spring.datasource.password: ${{ secrets.RDS_PASSWORD }}
+
+ - name: Set AWS Yml
+ uses: microsoft/variable-substitution@v1
+ with:
+ files: ${{ env.RESOURCE_AWS_PATH }}
+ env:
+ cloud.aws.credentials.accessKey: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ cloud.aws.credentials.secretKey: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+
+ - name: Set JWT Yml
+ uses: microsoft/variable-substitution@v1
+ with:
+ files: ${{ env.RESOURCE_JWT_PATH }}
+ env:
+ jwt.secret: ${{ secrets.JWT_SECRET }}
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ shell: bash
+
+ - name: Build with Gradle
+ run: ./gradlew build -x test
+ shell: bash
+
+ - name: Make zip file
+ run: zip -r ./$GITHUB_SHA.zip .
+ shell: bash
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ap-northeast-2
+
+ - name: Upload to S3
+ run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
+
+ - name: Code Deploy
+ run: aws deploy create-deployment --application-name uckgisagi-code-deploy --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name uckgisagi-code-deploy-group --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip
diff --git a/.gitignore b/.gitignore
index 1d83520e..8ec90e8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,9 +36,8 @@ out/
### VS Code ###
.vscode/
-
-### Firebase Admin ###
-src/main/resources/firebase/*.json
-
### querydsl ###
-src/querydsl
\ No newline at end of file
+src/querydsl
+
+### h2 database ###
+database
\ No newline at end of file
diff --git a/README.md b/README.md
index 8e6c1b8f..6d56274e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,35 @@
# uckgisagi-server
-๐์ต์ง๋ก ์ง๊ตฌ๋ฅผ ์ฌ๋ํ๋ ์ง๊ตฌ์ธ๋ค๐ฑ
+# ๐ ์ต์ง๋ก ์ง๊ตฌ๋ฅผ ์ฌ๋ํ๋ ์ง๊ตฌ์ธ๋ค ๐
+
+
+
+## ๊ฐ์
+2022ํ๋
๋ 2ํ๊ธฐ ์คํ์์ค ๊ธฐ๋ฐ ๊ธฐ์ด ์ค๊ณ ํ๊ธฐ ํ๋ก์ ํธ ๊ณผ์
+## ๊น ์ปค๋ฐ ์ปจ๋ฒค์
+* `feat`: ์๋ก์ด ๊ธฐ๋ฅ ๊ฐ๋ฐ
+* `fix`: ๋ฒ๊ทธ ์์
+* `add`: ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ
+* `chore`: ๋น๋ ์
๋ฌด ์์ (build.gradle ์์ )
+* `refactor`: ์ฝ๋ ๋ฆฌํฉํ ๋ง
+* `style`: ์ฝ๋ ํฌ๋งทํ
, ์ฃผ์ ์ญ์ ๋ฑ ์ฝ๋ ๋ณ๊ฒฝ์ด ์๋ ๊ฒฝ์ฐ
+* `test`: ํ
์คํธ ์ฝ๋, ๋ฆฌํฉํ ๋ง ํ
์คํธ ์ฝ๋ ๊ฐ๋ฐ ๋ฐ ์ถ๊ฐ
+## ์ฝ๋ ์ปจ๋ฒค์
+
+
+## ์ฌ์ฉํ ์คํ
+### ๋ฐฑ์๋
+* ์คํ๋ง ๋ถํธ 2.7.4
+* ์ธ์ด: ์๋ฐ
+* JAVA 11, Gradle
+* MySQL
+* Spring JPA
+* QueryDsl
+* Spring Security
+* Swagger
+ * http://54.180.212.221/api/swagger-ui/index.html
+* Sl4j Logging
+* Validation
+* ๊ทธ ์ธ ์คํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
+
+### ๊ฐ์ฌํฉ๋๋ค
diff --git a/appspec.yml b/appspec.yml
new file mode 100644
index 00000000..9ba53b47
--- /dev/null
+++ b/appspec.yml
@@ -0,0 +1,24 @@
+version: 0.0
+os: linux
+files:
+ - source: /
+ destination: /home/ec2-user/uckgisagi-server/
+ overwrite: yes
+
+permissions:
+ - object: /
+ pattern: "**"
+ owner: ec2-user
+ group: ec2-user
+
+hooks:
+ ApplicationStart:
+ - location: scripts/run_new_was.sh
+ timeout: 180
+ runas: ec2-user
+# - location: scripts/health_check.sh
+# timeout: 180
+# runas: ec2-user
+ - location: scripts/switch.sh
+ timeout: 180
+ runas: ec2-user
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 8c9adb28..a24459b0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,7 +22,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
// aws
-// implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
+ implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
// jetbrains annotation
compileOnly 'org.jetbrains:annotations:23.0.0'
@@ -63,8 +63,7 @@ dependencies {
implementation 'org.json:json:20220924'
// swagger
- implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
- implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
+ implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'
}
jar {
diff --git a/scripts/health_check.sh b/scripts/health_check.sh
new file mode 100644
index 00000000..2353935b
--- /dev/null
+++ b/scripts/health_check.sh
@@ -0,0 +1,29 @@
+CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
+TARGET_PORT=0
+
+# Toggle port Number
+if [ ${CURRENT_PORT} -eq 8081 ]; then
+ TARGET_PORT=8082
+elif [ ${CURRENT_PORT} -eq 8082 ]; then
+ TARGET_PORT=8081
+else
+ echo "> No WAS is connected to nginx"
+ exit 1
+fi
+
+echo "> Start health check of WAS at 'http://127.0.0.1:${TARGET_PORT}' ..."
+
+for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10
+do
+ echo "> #${RETRY_COUNT} trying..."
+ RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${TARGET_PORT}/api/health)
+
+ if [ ${RESPONSE_CODE} -eq 200 ]; then
+ echo "> New WAS successfully running"
+ exit 0
+ elif [ ${RETRY_COUNT} -eq 10 ]; then
+ echo "> Health check failed."
+ exit 1
+ fi
+ sleep 10
+done
\ No newline at end of file
diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh
new file mode 100644
index 00000000..88b727b2
--- /dev/null
+++ b/scripts/run_new_was.sh
@@ -0,0 +1,23 @@
+CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
+TARGET_PORT=0
+
+echo "> Current port of running WAS is ${CURRENT_PORT}."
+
+if [ ${CURRENT_PORT} -eq 8081 ]; then
+ TARGET_PORT=8082
+elif [ ${CURRENT_PORT} -eq 8082 ]; then
+ TARGET_PORT=8081
+else
+ echo "> No WAS is connected to nginx"
+fi
+
+TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')
+
+if [ ! -z ${TARGET_PID} ]; then
+ echo "> Kill WAS running at ${TARGET_PORT}."
+ sudo kill ${TARGET_PID}
+fi
+
+nohup java -jar -Dserver.port=${TARGET_PORT} -Dspring.profiles.active=prod /home/ec2-user/uckgisagi-server/build/libs/*.jar > /home/ec2-user/nohup.out 2>&1 &
+echo "> Now new WAS runs at ${TARGET_PORT}."
+exit 0
\ No newline at end of file
diff --git a/scripts/switch.sh b/scripts/switch.sh
new file mode 100644
index 00000000..d384cae9
--- /dev/null
+++ b/scripts/switch.sh
@@ -0,0 +1,23 @@
+CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
+TARGET_PORT=0
+
+echo "> Nginx currently proxies to ${CURRENT_PORT}."
+
+# Toggle port number
+if [ ${CURRENT_PORT} -eq 8081 ]; then
+ TARGET_PORT=8082
+elif [ ${CURRENT_PORT} -eq 8082 ]; then
+ TARGET_PORT=8081
+else
+ echo "> No WAS is connected to nginx"
+ exit 1
+fi
+
+# Change proxying port into target port
+echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ec2-user/service_url.inc
+
+echo "> Now Nginx proxies to ${TARGET_PORT}."
+# Reload nginx
+sudo service nginx reload
+
+echo "> Nginx reloaded."
\ No newline at end of file
diff --git a/src/main/java/server/uckgisagi/UckgisagiApplication.java b/src/main/java/server/uckgisagi/UckgisagiApplication.java
index f6a38ec6..7ba66538 100644
--- a/src/main/java/server/uckgisagi/UckgisagiApplication.java
+++ b/src/main/java/server/uckgisagi/UckgisagiApplication.java
@@ -2,7 +2,13 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+@EnableJpaAuditing
+@EnableFeignClients
@SpringBootApplication
public class UckgisagiApplication {
diff --git a/src/main/java/server/uckgisagi/app/accusation/controller/AccusationController.java b/src/main/java/server/uckgisagi/app/accusation/controller/AccusationController.java
new file mode 100644
index 00000000..8d65f996
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/controller/AccusationController.java
@@ -0,0 +1,30 @@
+package server.uckgisagi.app.accusation.controller;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import server.uckgisagi.app.accusation.dto.AccusationPostReqDto;
+import server.uckgisagi.app.accusation.dto.AccusationPostResDto;
+import server.uckgisagi.app.accusation.service.AccusationService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.CREATED_ACCUSE_POST;
+
+@RestController
+@RequiredArgsConstructor
+public class AccusationController {
+
+ private final AccusationService accusationService;
+
+ @ApiOperation("[์ธ์ฆ] ๋๋ฌ๋ณด๊ธฐ ํ์ด์ง - ๊ฒ์๊ธ ์ ๊ณ ํ๊ธฐ")
+ @Auth
+ @PostMapping("/v1/post/accuse")
+ public ApiSuccessResponse accusePost(@RequestBody AccusationPostReqDto accusationPostReqDto, @ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(CREATED_ACCUSE_POST, accusationService.accusePost(accusationPostReqDto, userId));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/domain/entity/Accusation.java b/src/main/java/server/uckgisagi/app/accusation/domain/entity/Accusation.java
new file mode 100644
index 00000000..5f0962a9
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/domain/entity/Accusation.java
@@ -0,0 +1,45 @@
+package server.uckgisagi.app.accusation.domain.entity;
+
+import lombok.*;
+import server.uckgisagi.app.accusation.dto.AccusationPostResDto;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Accusation extends AuditingTimeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "accusation_id")
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "post_id")
+ private Post post;
+
+ @OneToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name="user_id")
+ private User user;
+
+
+ private Accusation(final Post post, final User user) {
+ this.post = post;
+ this.user = user;
+ }
+
+ public static Accusation newInstance(Post post, User user) {
+ return new Accusation(post, user);
+ }
+
+ public AccusationPostResDto toAccusePostResponseDto(Accusation accusation){
+ return AccusationPostResDto.builder()
+ .postId(accusation.getPost().getId())
+ .userId(accusation.getUser().getId())
+ .build();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationCustomRepository.java b/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationCustomRepository.java
new file mode 100644
index 00000000..14e38d3f
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationCustomRepository.java
@@ -0,0 +1,13 @@
+package server.uckgisagi.app.accusation.domain.repository;
+
+import server.uckgisagi.app.accusation.domain.entity.Accusation;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface AccusationCustomRepository{
+
+ List findAllByPostId(Long postId);
+
+ Optional findByUserIdAndPostId(Long userId, Long postId);
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationCustomRepositoryImpl.java b/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationCustomRepositoryImpl.java
new file mode 100644
index 00000000..05e655d0
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationCustomRepositoryImpl.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.app.accusation.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.accusation.domain.entity.Accusation;
+
+import java.util.List;
+import java.util.Optional;
+
+import static server.uckgisagi.app.accusation.domain.entity.QAccusation.accusation;
+
+
+@RequiredArgsConstructor
+public class AccusationCustomRepositoryImpl implements AccusationCustomRepository {
+
+ private final JPAQueryFactory query;
+
+
+ @Override
+ public List findAllByPostId(Long postId) {
+ return query.selectFrom(accusation)
+ .where(accusation.post.id.eq(postId))
+ .fetch();
+ }
+
+ @Override
+ public Optional findByUserIdAndPostId(Long userId, Long postId) {
+ return query.selectFrom(accusation)
+ .where(accusation.user.id.eq(userId))
+ .where(accusation.post.id.eq(postId))
+ .stream().findFirst();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationRepository.java b/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationRepository.java
new file mode 100644
index 00000000..02dc9c08
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/domain/repository/AccusationRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.accusation.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.accusation.domain.entity.Accusation;
+
+public interface AccusationRepository extends JpaRepository, AccusationCustomRepository{
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/dto/AccusationPostReqDto.java b/src/main/java/server/uckgisagi/app/accusation/dto/AccusationPostReqDto.java
new file mode 100644
index 00000000..b3ac39e4
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/dto/AccusationPostReqDto.java
@@ -0,0 +1,22 @@
+package server.uckgisagi.app.accusation.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import server.uckgisagi.app.accusation.domain.entity.Accusation;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.user.domain.entity.User;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AccusationPostReqDto {
+
+ private Long postId;
+
+ public Accusation toAccusationEntity(User user, Post post){
+ return Accusation.newInstance(post, user);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/dto/AccusationPostResDto.java b/src/main/java/server/uckgisagi/app/accusation/dto/AccusationPostResDto.java
new file mode 100644
index 00000000..fa9e9edf
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/dto/AccusationPostResDto.java
@@ -0,0 +1,17 @@
+package server.uckgisagi.app.accusation.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AccusationPostResDto {
+
+ private Long postId;
+
+ private Long userId;
+}
diff --git a/src/main/java/server/uckgisagi/app/accusation/service/AccusationService.java b/src/main/java/server/uckgisagi/app/accusation/service/AccusationService.java
new file mode 100644
index 00000000..a43f45f3
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/accusation/service/AccusationService.java
@@ -0,0 +1,56 @@
+package server.uckgisagi.app.accusation.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.accusation.dto.AccusationPostReqDto;
+import server.uckgisagi.app.accusation.dto.AccusationPostResDto;
+import server.uckgisagi.app.post.service.PostServiceUtils;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.common.exception.custom.ConflictException;
+import server.uckgisagi.app.accusation.domain.entity.Accusation;
+import server.uckgisagi.app.accusation.domain.repository.AccusationRepository;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.entity.enumerate.PostStatus;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.CONFLICT_EXCEPTION;
+
+@Service
+@RequiredArgsConstructor
+public class AccusationService {
+
+ private final AccusationRepository accusationRepository;
+ private final UserRepository userRepository;
+ private final PostRepository postRepository;
+
+
+
+ @Transactional
+ public AccusationPostResDto accusePost(AccusationPostReqDto accusationPostReqDto, Long userId){
+
+ Optional repetition = accusationRepository.findByUserIdAndPostId(userId, accusationPostReqDto.getPostId());
+ if(repetition.isPresent()){
+ // ํด๋น ์ ์ ๊ฐ ์ด๋ฏธ ํด๋น ๊ฒ์๋ฌผ์ ์ ๊ณ ํ ๋ด์ญ์ด ์์ ๊ฒฝ์ฐ, ๋ฆฌํด bad Request
+ throw new ConflictException(String.format("์ด๋ฏธ ํด๋น ๊ฒ์๊ธ์ ๋ํ ์ ๊ณ ๋ด์ญ์ด ์กด์ฌํฉ๋๋ค."), CONFLICT_EXCEPTION);
+ }
+
+ User user = UserServiceUtils.findByUserId(userRepository, userId);
+ Post post = PostServiceUtils.findByPostId(postRepository, accusationPostReqDto.getPostId());
+
+ Accusation accusation = accusationRepository.save(accusationPostReqDto.toAccusationEntity(user, post));
+
+ List accusations = accusationRepository.findAllByPostId(accusationPostReqDto.getPostId());
+ if(accusations.size() >= 10){
+ // ๊ฒ์๋ฌผ ์ ๊ณ ๊ฐ 10๋ฒ ์ด์์ผ ๊ฒฝ์ฐ ์ ๋ณด์ด๊ฒ ํ๋ ๋ก์ง
+ post.changeStatus(PostStatus.INACTIVE);
+ }
+
+ return accusation.toAccusePostResponseDto(accusation);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/AuthController.java b/src/main/java/server/uckgisagi/app/auth/AuthController.java
new file mode 100644
index 00000000..169f21f6
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/AuthController.java
@@ -0,0 +1,43 @@
+package server.uckgisagi.app.auth;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import server.uckgisagi.app.auth.dto.request.LoginRequest;
+import server.uckgisagi.app.auth.dto.request.TokenRequest;
+import server.uckgisagi.app.auth.dto.response.LoginResponse;
+import server.uckgisagi.app.auth.dto.response.TokenResponse;
+import server.uckgisagi.app.auth.provider.AuthServiceProvider;
+import server.uckgisagi.app.auth.service.AuthService;
+import server.uckgisagi.app.auth.service.CreateTokenService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import javax.validation.Valid;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.CREATED_REISSUE_TOKEN;
+
+@RestController
+@RequiredArgsConstructor
+public class AuthController {
+
+ private final AuthServiceProvider authServiceProvider;
+ private final CreateTokenService createTokenService;
+
+ @ApiOperation("ํ์๊ฐ์
๋ฐ ๋ก๊ทธ์ธ ํ์ด์ง - ํ์๊ฐ์
๋ฐ ๋ก๊ทธ์ธ")
+ @PostMapping("/v1/auth/login")
+ public ApiSuccessResponse login(@Valid @RequestBody LoginRequest request) {
+ AuthService authService = authServiceProvider.getAuthService(request.getSocialType());
+ User user = authService.login(request.toServiceDto());
+ TokenResponse tokenInfo = createTokenService.createTokenInfo(user.getId());
+ return ApiSuccessResponse.success(LoginResponse.of(user, tokenInfo));
+ }
+
+ @ApiOperation("ํ ํฐ ๋ง๋ฃ ์ ์์ธ์ค ํ ํฐ ์ฌ๋ฐ๊ธ ์์ฒญ")
+ @PostMapping("/v1/auth/reissue")
+ public ApiSuccessResponse reissueToken(@Valid @RequestBody TokenRequest request) {
+ return ApiSuccessResponse.success(CREATED_REISSUE_TOKEN, createTokenService.reissueToken(request));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/dto/request/LoginDto.java b/src/main/java/server/uckgisagi/app/auth/dto/request/LoginDto.java
new file mode 100644
index 00000000..25cb6481
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/dto/request/LoginDto.java
@@ -0,0 +1,24 @@
+package server.uckgisagi.app.auth.dto.request;
+
+import lombok.*;
+import server.uckgisagi.app.user.dto.request.CreateUserDto;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginDto {
+
+ private String socialAccessToken;
+ private SocialType socialType;
+ private String fcmToken;
+
+ public static LoginDto of(String socialAccessToken, SocialType socialType, String fcmToken) {
+ return new LoginDto(socialAccessToken, socialType, fcmToken);
+ }
+
+ public CreateUserDto toCreateUserDto(String socialId, String nickname, String fcmToken) {
+ return CreateUserDto.of(nickname, socialId, socialType, fcmToken);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/dto/request/LoginRequest.java b/src/main/java/server/uckgisagi/app/auth/dto/request/LoginRequest.java
new file mode 100644
index 00000000..d4e0d705
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/dto/request/LoginRequest.java
@@ -0,0 +1,27 @@
+package server.uckgisagi.app.auth.dto.request;
+
+import lombok.*;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginRequest {
+
+ @NotBlank(message = "{auth.accessToken.notBlank}")
+ private String socialToken;
+
+ @NotNull(message = "{auth.socialType.notNull}")
+ private SocialType socialType;
+
+ @NotBlank(message = "{auth.fcmToken.notBlank}")
+ private String fcmToken;
+
+ public LoginDto toServiceDto() {
+ return LoginDto.of(socialToken, socialType, fcmToken);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/dto/request/TokenRequest.java b/src/main/java/server/uckgisagi/app/auth/dto/request/TokenRequest.java
new file mode 100644
index 00000000..3cb07b82
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/dto/request/TokenRequest.java
@@ -0,0 +1,19 @@
+package server.uckgisagi.app.auth.dto.request;
+
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class TokenRequest {
+
+ @NotBlank(message = "${auth.accessToken.notBlank}")
+ private String accessToken;
+
+ @NotBlank(message = "${auth.refreshToken.notBlank}")
+ private String refreshToken;
+
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/dto/response/LoginResponse.java b/src/main/java/server/uckgisagi/app/auth/dto/response/LoginResponse.java
new file mode 100644
index 00000000..f3a7c25c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/dto/response/LoginResponse.java
@@ -0,0 +1,21 @@
+package server.uckgisagi.app.auth.dto.response;
+
+import lombok.*;
+import server.uckgisagi.app.user.domain.entity.User;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginResponse {
+
+ private Long userId;
+ private String nickname;
+ private String accessToken;
+ private String refreshToken;
+
+ public static LoginResponse of(User user, TokenResponse token) {
+ return new LoginResponse(user.getId(), user.getNickname(), token.getAccessToken(), token.getRefreshToken());
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/dto/response/TokenResponse.java b/src/main/java/server/uckgisagi/app/auth/dto/response/TokenResponse.java
new file mode 100644
index 00000000..d597f0ec
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/dto/response/TokenResponse.java
@@ -0,0 +1,18 @@
+package server.uckgisagi.app.auth.dto.response;
+
+import lombok.*;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class TokenResponse {
+
+ private String accessToken;
+ private String refreshToken;
+
+ public static TokenResponse of(String accessToken, String refreshToken) {
+ return new TokenResponse(accessToken, refreshToken);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/provider/AuthServiceProvider.java b/src/main/java/server/uckgisagi/app/auth/provider/AuthServiceProvider.java
new file mode 100644
index 00000000..0b669fbf
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/provider/AuthServiceProvider.java
@@ -0,0 +1,29 @@
+package server.uckgisagi.app.auth.provider;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import server.uckgisagi.app.auth.service.AuthService;
+import server.uckgisagi.app.auth.service.impl.AppleAuthService;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class AuthServiceProvider {
+
+ private static final Map authServiceMap = new HashMap<>();
+
+ private final AppleAuthService appleAuthService;
+
+ @PostConstruct // ์์กด์ฑ ์ฃผ์
์ด ์๋ฃ๋ ํ ์ด๊ธฐํ
+ public void initAuthServiceMap() {
+ authServiceMap.put(SocialType.APPLE, appleAuthService);
+ }
+
+ public AuthService getAuthService(SocialType socialType) {
+ return authServiceMap.get(socialType);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/service/AuthService.java b/src/main/java/server/uckgisagi/app/auth/service/AuthService.java
new file mode 100644
index 00000000..c3a7f724
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/service/AuthService.java
@@ -0,0 +1,8 @@
+package server.uckgisagi.app.auth.service;
+
+import server.uckgisagi.app.auth.dto.request.LoginDto;
+import server.uckgisagi.app.user.domain.entity.User;
+
+public interface AuthService {
+ User login(LoginDto request);
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/service/CreateTokenService.java b/src/main/java/server/uckgisagi/app/auth/service/CreateTokenService.java
new file mode 100644
index 00000000..3c455b8b
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/service/CreateTokenService.java
@@ -0,0 +1,31 @@
+package server.uckgisagi.app.auth.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.auth.dto.request.TokenRequest;
+import server.uckgisagi.app.auth.dto.response.TokenResponse;
+import server.uckgisagi.common.exception.custom.UnAuthorizedException;
+import server.uckgisagi.common.util.JwtUtils;
+
+@Service
+@RequiredArgsConstructor
+public class CreateTokenService {
+
+ private final JwtUtils jwtProvider;
+
+ @Transactional
+ public TokenResponse createTokenInfo(Long userId) {
+ return jwtProvider.createTokenByUserId(userId);
+ }
+
+ @Transactional
+ public TokenResponse reissueToken(TokenRequest request) {
+ if (!jwtProvider.validateToken(request.getRefreshToken())) {
+ throw new UnAuthorizedException(String.format("๋ฆฌํ๋ ์ ํ ํฐ (%s) ์ด ์ ํจํ์ง ์์ต๋๋ค", request.getRefreshToken()));
+ }
+ Long userId = jwtProvider.getUserIdFromJwt(request.getAccessToken());
+ return jwtProvider.createTokenByUserId(userId);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/auth/service/impl/AppleAuthService.java b/src/main/java/server/uckgisagi/app/auth/service/impl/AppleAuthService.java
new file mode 100644
index 00000000..bf5e1f59
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/auth/service/impl/AppleAuthService.java
@@ -0,0 +1,34 @@
+package server.uckgisagi.app.auth.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import server.uckgisagi.app.auth.dto.request.LoginDto;
+import server.uckgisagi.app.auth.service.AuthService;
+import server.uckgisagi.app.user.service.UserService;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.common.util.RandomNicknameUtils;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+import server.uckgisagi.external.client.apple.AppleTokenDecoder;
+
+@Service
+@RequiredArgsConstructor
+public class AppleAuthService implements AuthService {
+
+ private static final SocialType SOCIAL_TYPE = SocialType.APPLE;
+
+ private final AppleTokenDecoder appleTokenDecoder;
+ private final UserService userService;
+ private final UserRepository userRepository;
+
+ @Override
+ public User login(LoginDto request) {
+ String socialId = appleTokenDecoder.getSocialIdFromIdToken(request.getSocialAccessToken());
+ User user = UserServiceUtils.findUserBySocialIdAndSocialType(userRepository, socialId, SOCIAL_TYPE);
+ if (user == null) {
+ return userService.registerUser(request.toCreateUserDto(socialId, RandomNicknameUtils.generate(), request.getFcmToken()));
+ }
+ return user;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/block/controller/BlockController.java b/src/main/java/server/uckgisagi/app/block/controller/BlockController.java
new file mode 100644
index 00000000..046935da
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/controller/BlockController.java
@@ -0,0 +1,48 @@
+package server.uckgisagi.app.block.controller;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import server.uckgisagi.app.block.dto.BlockUserDto;
+import server.uckgisagi.app.block.service.BlockService;
+import server.uckgisagi.app.post.dto.response.PreviewPostResponse;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class BlockController {
+
+ private final BlockService blockService;
+
+ @ApiOperation("[์ธ์ฆ] ์ ์ ์ฐจ๋จ ํ์ด์ง - ์ ์ ์ฐจ๋จํ๊ธฐ")
+ @Auth
+ @PostMapping("/v1/block")
+ public ApiSuccessResponse blockUser(@RequestBody BlockUserDto blockUserRequestDto, @ApiIgnore @LoginUserId Long userId) {
+ blockService.blockUser(blockUserRequestDto, userId);
+ return ApiSuccessResponse.success(NO_CONTENT_BLOCK_USER);
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์ ์ ์ฐจ๋จ ๋ชฉ๋ก ํ์ด์ง - ์ฐจ๋จํ ์ ์ ๋ชฉ๋ก ์กฐํํ๊ธฐ")
+ @Auth
+ @GetMapping("/v1/block/retrieve")
+ public ApiSuccessResponse> retrieveAllBlockedUser(@ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_ALL_POST, blockService.retrieveAllBlockedUser(userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์ ์ ์ฐจ๋จ ํด์ ํ์ด์ง - ์ ์ ์ฐจ๋จ ํด์ ํ๊ธฐ")
+ @Auth
+ @DeleteMapping("/v1/block/delete")
+ public ApiSuccessResponse deleteBlockUser(@RequestParam Long blockUserId, @ApiIgnore @LoginUserId Long userId) {
+ blockService.deleteBlockUser(blockUserId, userId);
+ return ApiSuccessResponse.success(NO_CONTENT_CANCEL_BLOCK_USER);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/block/domain/entity/Block.java b/src/main/java/server/uckgisagi/app/block/domain/entity/Block.java
new file mode 100644
index 00000000..d886dc2d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/domain/entity/Block.java
@@ -0,0 +1,32 @@
+package server.uckgisagi.app.block.domain.entity;
+
+import lombok.*;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Block {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "block_id")
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name="user_id")
+ private User user;
+
+ private Long blockUserId;
+
+ private Block(final User user, final Long blockUserId){
+ this.user = user;
+ this.blockUserId = blockUserId;
+ }
+
+ public static Block newInstance(User user, Long blockUserId){
+ return new Block(user, blockUserId);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepository.java b/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepository.java
new file mode 100644
index 00000000..aa44b53f
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepository.java
@@ -0,0 +1,9 @@
+package server.uckgisagi.app.block.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import server.uckgisagi.app.block.domain.entity.Block;
+
+@Repository
+public interface BlockRepository extends JpaRepository, BlockRepositoryCustom {
+}
diff --git a/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepositoryCustom.java b/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepositoryCustom.java
new file mode 100644
index 00000000..2efbb58c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepositoryCustom.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.block.domain.repository;
+
+import server.uckgisagi.app.block.domain.entity.Block;
+
+public interface BlockRepositoryCustom {
+ Block findByBlockUserId(Long blockUserId, Long userId);
+}
diff --git a/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepositoryCustomImpl.java
new file mode 100644
index 00000000..e193219e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/domain/repository/BlockRepositoryCustomImpl.java
@@ -0,0 +1,20 @@
+package server.uckgisagi.app.block.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.block.domain.entity.Block;
+import static server.uckgisagi.app.block.domain.entity.QBlock.block;
+
+@RequiredArgsConstructor
+public class BlockRepositoryCustomImpl implements BlockRepositoryCustom{
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public Block findByBlockUserId(Long blockUserId, Long userId) {
+ return query.selectFrom(block)
+ .where(block.user.id.eq(userId))
+ .where(block.blockUserId.eq(blockUserId))
+ .fetchOne();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/block/dto/BlockUserDto.java b/src/main/java/server/uckgisagi/app/block/dto/BlockUserDto.java
new file mode 100644
index 00000000..be2bc0e0
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/dto/BlockUserDto.java
@@ -0,0 +1,22 @@
+package server.uckgisagi.app.block.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class BlockUserDto {
+
+ private Long blockUserId;
+
+ public static BlockUserDto of(Long blockUserId) {
+ return BlockUserDto.builder()
+ .blockUserId(blockUserId)
+ .build();
+ }
+}
+
diff --git a/src/main/java/server/uckgisagi/app/block/service/BlockService.java b/src/main/java/server/uckgisagi/app/block/service/BlockService.java
new file mode 100644
index 00000000..a6e33e6d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/block/service/BlockService.java
@@ -0,0 +1,56 @@
+package server.uckgisagi.app.block.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.block.dto.BlockUserDto;
+import server.uckgisagi.app.follow.service.FollowService;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.app.block.domain.entity.Block;
+import server.uckgisagi.app.block.domain.repository.BlockRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BlockService {
+
+ private final UserRepository userRepository;
+ private final FollowService followService;
+ private final BlockRepository blockRepository;
+
+ @Transactional
+ public void blockUser(BlockUserDto blockUserDto, Long userId) {
+ // ์ผ๋จ ์ฐจ๋จํ๊ณ ์ ํ๋ ์ ์ unfollow
+ User user = userRepository.findUserByUserId(userId); // ์ฐจ๋จ ๋ฒํผ ๋๋ฅด๋ ์ ์
+ User blockUser = userRepository.findUserByUserId(blockUserDto.getBlockUserId()); // ์ฐจ๋จ ๋นํ๋ ์ ์
+ followService.unfollowUser(blockUser.getId(), userId);
+
+ // Block ํ
์ด๋ธ์ ๋ฐ์ดํฐ ์ถ๊ฐ
+ blockRepository.save(Block.newInstance(user, blockUser.getId()));
+ }
+
+ @Transactional
+ public List retrieveAllBlockedUser(Long userId){
+ User me = UserServiceUtils.findByUserId(userRepository, userId);
+ List blockUserIds = me.getBlocks().stream()
+ .map(Block::getBlockUserId)
+ .collect(Collectors.toList());
+
+ return blockUserIds.stream()
+ .map(id -> BlockUserDto.of(id))
+ .collect(Collectors.toList());
+ }
+
+ @Transactional
+ public void deleteBlockUser(Long blockUserId, Long userId) {
+ User blockUser = userRepository.findUserByUserId(blockUserId);
+ Block block = blockRepository.findByBlockUserId(blockUser.getId(), userId);
+
+ blockRepository.delete(block);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/follow/FollowController.java b/src/main/java/server/uckgisagi/app/follow/FollowController.java
new file mode 100644
index 00000000..3367cdc2
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/follow/FollowController.java
@@ -0,0 +1,41 @@
+package server.uckgisagi.app.follow;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import server.uckgisagi.app.follow.service.FollowService;
+import server.uckgisagi.app.notification.provider.NotificationServiceProvider;
+import server.uckgisagi.app.notification.service.NotificationService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+import springfox.documentation.annotations.ApiIgnore;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class FollowController {
+
+ private final FollowService followService;
+ private final NotificationServiceProvider notificationServiceProvider;
+
+ @ApiOperation("[์ธ์ฆ] ์ ์ ๊ฒ์ ํ์ด์ง - ํ๋ก์ฐ ์ ์ฒญํ๊ธฐ")
+ @Auth
+ @PostMapping("/v1/follow/{targetUserId}")
+ public ApiSuccessResponse followUser(@PathVariable Long targetUserId, @ApiIgnore @LoginUserId Long userId) {
+ NotificationService notificationService = notificationServiceProvider.getNotificationService(NotificationType.FOLLOW);
+ notificationService.sendNotification(userId, targetUserId, followService.followUser(targetUserId, userId));
+ return ApiSuccessResponse.success(CREATED_NOTIFICATION);
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์ ์ ๊ฒ์ ํ์ด์ง - ์ธํ๋ก์ฐ ํ๊ธฐ")
+ @Auth
+ @DeleteMapping("/v1/unfollow/{targetUserId}")
+ public ApiSuccessResponse unfollowUser(@PathVariable Long targetUserId, @ApiIgnore @LoginUserId Long userId) {
+ followService.unfollowUser(targetUserId, userId);
+ return ApiSuccessResponse.success(NO_CONTENT_UNFOLLOW_USER);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/follow/entity/Follow.java b/src/main/java/server/uckgisagi/app/follow/domain/entity/Follow.java
similarity index 77%
rename from src/main/java/server/uckgisagi/domain/follow/entity/Follow.java
rename to src/main/java/server/uckgisagi/app/follow/domain/entity/Follow.java
index 84942166..ab32faab 100644
--- a/src/main/java/server/uckgisagi/domain/follow/entity/Follow.java
+++ b/src/main/java/server/uckgisagi/app/follow/domain/entity/Follow.java
@@ -1,10 +1,10 @@
-package server.uckgisagi.domain.follow.entity;
+package server.uckgisagi.app.follow.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
-import server.uckgisagi.domain.user.entity.User;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+import server.uckgisagi.app.user.domain.entity.User;
import javax.persistence.*;
@@ -31,8 +31,7 @@ private Follow(final User followee, final User follower) {
this.follower = follower;
}
- public static Follow of(User followee, User follower) {
+ public static Follow newInstance(User followee, User follower) {
return new Follow(followee, follower);
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepository.java b/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepository.java
new file mode 100644
index 00000000..828d679f
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.follow.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+
+public interface FollowRepository extends JpaRepository, FollowRepositoryCustom {
+}
diff --git a/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepositoryCustom.java b/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepositoryCustom.java
new file mode 100644
index 00000000..98b335fd
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepositoryCustom.java
@@ -0,0 +1,12 @@
+package server.uckgisagi.app.follow.domain.repository;
+
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.util.List;
+
+public interface FollowRepositoryCustom {
+ boolean existsByTargetUserIdAndUserId(Long targetUserId, Long userId);
+ List findMyFollowingUserByUserId(Long userId);
+ Follow findFollowByFolloweeUserIdAndFollowerUserId(Long followeeUserId, Long followerUserId);
+}
diff --git a/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepositoryCustomImpl.java
new file mode 100644
index 00000000..480a79fa
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/follow/domain/repository/FollowRepositoryCustomImpl.java
@@ -0,0 +1,46 @@
+package server.uckgisagi.app.follow.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.util.List;
+
+import static server.uckgisagi.app.follow.domain.entity.QFollow.*;
+
+@RequiredArgsConstructor
+public class FollowRepositoryCustomImpl implements FollowRepositoryCustom {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public boolean existsByTargetUserIdAndUserId(Long targetUserId, Long userId) {
+ return query
+ .selectOne()
+ .from(follow)
+ .where(
+ follow.followee.id.eq(targetUserId),
+ follow.follower.id.eq(userId)
+ ).fetchFirst() != null;
+ }
+
+ @Override
+ public List findMyFollowingUserByUserId(Long userId) {
+ return query
+ .select(follow.followee).distinct()
+ .from(follow)
+ .where(follow.follower.id.in(userId))
+ .fetch();
+ }
+
+ @Override
+ public Follow findFollowByFolloweeUserIdAndFollowerUserId(Long followeeUserId, Long followerUserId) {
+ return query
+ .selectFrom(follow)
+ .where(
+ follow.followee.id.eq(followeeUserId),
+ follow.follower.id.eq(followerUserId)
+ ).fetchOne();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/follow/service/FollowService.java b/src/main/java/server/uckgisagi/app/follow/service/FollowService.java
new file mode 100644
index 00000000..1c33240d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/follow/service/FollowService.java
@@ -0,0 +1,47 @@
+package server.uckgisagi.app.follow.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.user.domain.dictionary.UserDictionary;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+
+// FIXME: 2022/11/18 user ์ฐ๊ด๊ด๊ณ ๋ฉ์๋ ์ค๋ฅ
+@Service
+@RequiredArgsConstructor
+public class FollowService {
+
+ private final UserRepository userRepository;
+ private final FollowRepository followRepository;
+
+ @Transactional
+ public UserDictionary followUser(Long targetUserId, Long userId) {
+ User me = UserServiceUtils.findByUserId(userRepository, userId);
+ User targetUser = UserServiceUtils.findByUserId(userRepository, targetUserId);
+
+ FollowServiceUtils.validateNotFollowingUser(followRepository, targetUser.getId(), me.getId());
+
+ followRepository.save(Follow.newInstance(targetUser, me));
+// targetUser.addFollower(followInfo);
+// me.addFollowing(followInfo);
+
+ return UserDictionary.from(List.of(me, targetUser));
+ }
+
+ @Transactional
+ public void unfollowUser(Long targetUserId, Long userId) {
+ UserServiceUtils.findByUserId(userRepository, userId);
+ UserServiceUtils.findByUserId(userRepository, targetUserId);
+
+// me.deleteFollowing(followInfo);
+// friend.deleteFollower(followInfo);
+
+ followRepository.delete(FollowServiceUtils.findByFolloweeUserIdAndFollowerUserId(followRepository, targetUserId, userId));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/follow/service/FollowServiceUtils.java b/src/main/java/server/uckgisagi/app/follow/service/FollowServiceUtils.java
new file mode 100644
index 00000000..25425861
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/follow/service/FollowServiceUtils.java
@@ -0,0 +1,27 @@
+package server.uckgisagi.app.follow.service;
+
+import org.jetbrains.annotations.NotNull;
+import server.uckgisagi.common.exception.custom.ConflictException;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+public class FollowServiceUtils {
+
+ @NotNull
+ public static Follow findByFolloweeUserIdAndFollowerUserId(FollowRepository followRepository, Long followeeUserId, Long followerUserId) {
+ Follow follow = followRepository.findFollowByFolloweeUserIdAndFollowerUserId(followeeUserId, followerUserId);
+ if (follow == null) {
+ throw new NotFoundException(String.format("์กด์ฌํ์ง ์๋ ํ๋ก์ฐ ๋์ - ํ๋ก์ (%s - %s) ๊ด๊ณ ์
๋๋ค", followeeUserId, followerUserId), NOT_FOUND_FOLLOW_RELATION_EXCEPTION);
+ }
+ return follow;
+ }
+
+ public static void validateNotFollowingUser(FollowRepository followRepository, Long targetUserId, Long userId) {
+ if (followRepository.existsByTargetUserIdAndUserId(targetUserId, userId)) {
+ throw new ConflictException(String.format("์ด๋ฏธ ํ๋ก์ฐ์ค์ธ ์ ์ (%s) ์
๋๋ค", targetUserId), CONFLICT_ALREADY_EXIST_FOLLOW_EXCEPTION);
+ }
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/home/HomeController.java b/src/main/java/server/uckgisagi/app/home/HomeController.java
new file mode 100644
index 00000000..ea5664df
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/HomeController.java
@@ -0,0 +1,62 @@
+package server.uckgisagi.app.home;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import server.uckgisagi.app.home.dto.response.HomePostResponse;
+import server.uckgisagi.app.home.dto.response.HomeUserResponse;
+import server.uckgisagi.app.home.service.HomeRetrieveService;
+import server.uckgisagi.app.post.dto.response.PostResponse;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class HomeController {
+
+ private final HomeRetrieveService homeRetrieveService;
+
+ @ApiOperation("[์ธ์ฆ] ๋ฉ์ธ ํ ํ์ด์ง - ๋์ ์น๊ตฌ ์ ๋ณด ๋ณด๊ธฐ")
+ @Auth
+ @GetMapping("/v1/home/user")
+ public ApiSuccessResponse retrieveMeAndFriendInfo(@ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_MY_HOME_CONTENTS, homeRetrieveService.retrieveMeAndFriendInfo(userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ๋ฉ์ธ ํ ํ์ด์ง - ๋์ ํฌ์คํธ ์ ๋ณด ๋ณด๊ธฐ")
+ @Auth
+ @GetMapping("/v1/home/me")
+ public ApiSuccessResponse retrieveMyHomeContents(@ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_MY_HOME_CONTENTS, homeRetrieveService.retrieveHomeContents(userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ๋ฉ์ธ ํ ํ์ด์ง - ๋ ์ง๋ก ๋์ ํฌ์คํธ ์ ๋ณด ์กฐํํ๊ธฐ")
+ @Auth
+ @GetMapping("/v1/home/me/post")
+ public ApiSuccessResponse> retrieveMyPostByDate(@RequestParam String date, @ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_MY_HOME_CONTENTS, homeRetrieveService.retrievePostInfoByDate(date, userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ๋ฉ์ธ ํ ํ์ด์ง - ์น๊ตฌ์ ํฌ์คํธ ์ ๋ณด ๋ณด๊ธฐ")
+ @Auth
+ @GetMapping("/v1/home/{friendUserId}")
+ public ApiSuccessResponse retrieveFriendHomeContents(@PathVariable Long friendUserId) {
+ return ApiSuccessResponse.success(OK_SEARCH_FRIEND_HOME_CONTENTS, homeRetrieveService.retrieveHomeContents(friendUserId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ๋ฉ์ธ ํ ํ์ด์ง - ๋ ์ง๋ก ์น๊ตฌ์ ํฌ์คํธ ์ ๋ณด ์กฐํํ๊ธฐ")
+ @Auth
+ @GetMapping("/v1/home/{friendUserId}/post")
+ public ApiSuccessResponse> retrieveFriendPostByDate(@RequestParam String date, @PathVariable Long friendUserId) {
+ return ApiSuccessResponse.success(OK_SEARCH_MY_HOME_CONTENTS, homeRetrieveService.retrievePostInfoByDate(date, friendUserId));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/home/dto/response/HomePostResponse.java b/src/main/java/server/uckgisagi/app/home/dto/response/HomePostResponse.java
new file mode 100644
index 00000000..a0d2cd08
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/dto/response/HomePostResponse.java
@@ -0,0 +1,22 @@
+package server.uckgisagi.app.home.dto.response;
+
+import lombok.*;
+import server.uckgisagi.app.post.dto.response.PostResponse;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class HomePostResponse {
+
+ private List postDates;
+ private List posts;
+
+ public static HomePostResponse of(List postDates, List posts) {
+ return new HomePostResponse(postDates, posts);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/home/dto/response/HomeUserResponse.java b/src/main/java/server/uckgisagi/app/home/dto/response/HomeUserResponse.java
new file mode 100644
index 00000000..aa48074e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/dto/response/HomeUserResponse.java
@@ -0,0 +1,20 @@
+package server.uckgisagi.app.home.dto.response;
+
+import lombok.*;
+
+import java.util.List;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class HomeUserResponse {
+
+ private UserResponseDto myInfo;
+ private List friendInfo;
+
+ public static HomeUserResponse of(UserResponseDto myInfo, List friendInfo) {
+ return new HomeUserResponse(myInfo, friendInfo);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/home/dto/response/TodayPostStatus.java b/src/main/java/server/uckgisagi/app/home/dto/response/TodayPostStatus.java
new file mode 100644
index 00000000..2dab426d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/dto/response/TodayPostStatus.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.home.dto.response;
+
+public enum TodayPostStatus {
+ ACTIVE,
+ INACTIVE,
+ ;
+}
diff --git a/src/main/java/server/uckgisagi/app/home/dto/response/UserResponseDto.java b/src/main/java/server/uckgisagi/app/home/dto/response/UserResponseDto.java
new file mode 100644
index 00000000..d81b0a8c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/dto/response/UserResponseDto.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.app.home.dto.response;
+
+import lombok.*;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.UserGrade;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class UserResponseDto {
+
+ private Long userId;
+ private String nickname;
+ private UserGrade grade;
+ private TodayPostStatus postStatus;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private UserResponseDto(final Long userId, final String nickname, final UserGrade grade, final TodayPostStatus postStatus) {
+ this.userId = userId;
+ this.nickname = nickname;
+ this.grade = grade;
+ this.postStatus = postStatus;
+ }
+
+ public static UserResponseDto of(User user, TodayPostStatus postStatus) {
+ return UserResponseDto.builder()
+ .userId(user.getId())
+ .nickname(user.getNickname())
+ .grade(user.getGrade())
+ .postStatus(postStatus)
+ .build();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/home/service/HomeRetrieveService.java b/src/main/java/server/uckgisagi/app/home/service/HomeRetrieveService.java
new file mode 100644
index 00000000..66cc6fcb
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/service/HomeRetrieveService.java
@@ -0,0 +1,116 @@
+package server.uckgisagi.app.home.service;
+
+import lombok.RequiredArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.home.dto.response.*;
+import server.uckgisagi.app.post.dto.response.PostResponse;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class HomeRetrieveService implements HomeService {
+
+ private final UserRepository userRepository;
+ private final PostRepository postRepository;
+ private final FollowRepository followRepository;
+
+ private final LocalDate TODAY_DATE = LocalDate.now(ZoneId.of("Asia/Seoul"));
+ private final LocalDate THIS_MONTH_DATE = LocalDate.of(TODAY_DATE.getYear(), TODAY_DATE.getMonthValue(), START_DAY_OF_MONTH);
+
+ private static final int START_DAY_OF_MONTH = 1;
+ private static final long ONE_MONTH = 1L;
+
+ @Override
+ @Transactional(readOnly = true)
+ public HomeUserResponse retrieveMeAndFriendInfo(Long userId) {
+ User user = UserServiceUtils.findByUserId(userRepository, userId);
+
+ UserResponseDto myInfoResponseDto = getMyInfoResponseDto(user);
+ List friendsInfoResponseDto = getFriendsInfoResponseDto(user);
+
+ return HomeUserResponse.of(myInfoResponseDto, friendsInfoResponseDto);
+ }
+
+ private List getFriendsInfoResponseDto(User user) {
+// List friends = user.getMyFollowings();
+ List friends = followRepository.findMyFollowingUserByUserId(user.getId());
+ List friendsIds = friends.stream()
+ .map(User::getId)
+ .collect(Collectors.toList());
+ List todayPostUsers = postRepository.findUserIdsByTodayDate(TODAY_DATE, friendsIds);
+
+ return friends.stream()
+ .map(friend -> todayPostUsers.contains(friend)
+ ? UserResponseDto.of(friend, TodayPostStatus.ACTIVE)
+ : UserResponseDto.of(friend, TodayPostStatus.INACTIVE)
+ )
+ .collect(Collectors.toList());
+ }
+
+ private UserResponseDto getMyInfoResponseDto(User user) {
+ return UserResponseDto.of(
+ user,
+ postRepository.existsByTodayDate(TODAY_DATE, user.getId())
+ ? TodayPostStatus.ACTIVE
+ : TodayPostStatus.INACTIVE
+ );
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public HomePostResponse retrieveHomeContents(Long userId) {
+ User user = UserServiceUtils.findByUserId(userRepository, userId);
+ return getHomePostResponse(postRepository.findPostByUserId(user.getId()));
+ }
+
+ private HomePostResponse getHomePostResponse(List postByUserId) {
+ return HomePostResponse.of(
+ getPostDatesInThisMonth(postByUserId),
+ getPostResponses(postByUserId)
+ );
+ }
+
+ @Nullable
+ private List getPostDatesInThisMonth(List posts) {
+ return posts.stream()
+ .map(post -> {
+ LocalDate postCreatedAt = post.getCreatedAt().toLocalDate();
+ return isWithinThisMonth(postCreatedAt) ? postCreatedAt : null;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private boolean isWithinThisMonth(LocalDate postCreatedAt) {
+ return postCreatedAt.getDayOfMonth() - START_DAY_OF_MONTH != 0
+ ? postCreatedAt.isAfter(THIS_MONTH_DATE) && postCreatedAt.isBefore(THIS_MONTH_DATE.plusMonths(ONE_MONTH))
+ : postCreatedAt.isEqual(THIS_MONTH_DATE);
+ }
+
+ @Transactional(readOnly = true)
+ public List retrievePostInfoByDate(String date, Long userId) {
+ return getPostResponses(postRepository.findPostByDateAndUserId(LocalDate.parse(date, DateTimeFormatter.ISO_DATE), userId));
+ }
+
+ @NotNull
+ private List getPostResponses(List posts) {
+ return posts.stream()
+ .map(PostResponse::from)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/home/service/HomeService.java b/src/main/java/server/uckgisagi/app/home/service/HomeService.java
new file mode 100644
index 00000000..267cae0a
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/home/service/HomeService.java
@@ -0,0 +1,25 @@
+package server.uckgisagi.app.home.service;
+
+import server.uckgisagi.app.home.dto.response.HomePostResponse;
+import server.uckgisagi.app.home.dto.response.HomeUserResponse;
+
+/**
+ * @see HomeUserResponse
+ * @see HomePostResponse
+ * @see HomeRetrieveService
+ */
+public interface HomeService {
+ /**
+ * ํ ์๋จ์ ๋์ค๋ ์ ์ ์ ์ ๋ณด์ ์ ์ ์ ์น๊ตฌ ์ ๋ณด ์กฐํ
+ * @param userId ์ ์ ์์ด๋
+ * @return ์ ์ ์ ์ ๋ณด์ ์ ์ ์ ์น๊ตฌ ์ ๋ณด
+ */
+ HomeUserResponse retrieveMeAndFriendInfo(Long userId);
+
+ /**
+ * ํ ์ปจํ
์ธ ์กฐํ
+ * @param userId ์ ์ ์์ด๋ ํน์ ์น๊ตฌ์ ์ ์ ์์ด๋
+ * @return ํด๋น ์ ์ ์ ์ด๋ฒ๋ฌ ์ฑ๋ฆฐ์ง ๊ธ ์์ฑ ์ฌ๋ถ ์บ๋ฆฐ๋ ์ ๋ณด์ ์ฑ๋ฆฐ์ง ๊ธ ์ ๋ณด
+ */
+ HomePostResponse retrieveHomeContents(Long userId);
+}
diff --git a/src/main/java/server/uckgisagi/app/image/client/FileStorageClient.java b/src/main/java/server/uckgisagi/app/image/client/FileStorageClient.java
new file mode 100644
index 00000000..c92e6d64
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/image/client/FileStorageClient.java
@@ -0,0 +1,27 @@
+package server.uckgisagi.app.image.client;
+
+import org.springframework.web.multipart.MultipartFile;
+
+public interface FileStorageClient {
+
+ /**
+ * AmazonS3 Bucket ์ ํ์ผ์ ์ ์ฅํ๋ค
+ * @param file ์ ์ ๊ฐ ๋ฑ๋กํ๋ ค ํ๋ ํ์ผ
+ * @param fileName ํ์ผ์ ๊ธฐ์กด์ ํ์ฅ์๋ฅผ ์ ์งํ ์ฑ ๋ฐํ๋ ์ ๋ํฌํ ํ์ผ์ ์ด๋ฆ
+ */
+ void uploadFile(MultipartFile file, String fileName);
+
+ /**
+ * AmazonS3 Bucket ์ ์ ์ฅ๋ ํ์ผ ๊ฒฝ๋ก url ์ ๊ฐ์ ธ์จ๋ค
+ * @param fileName ํ์ผ์ ๊ธฐ์กด์ ํ์ฅ์๋ฅผ ์ ์งํ ์ฑ ๋ฐํ๋ ์ ๋ํฌํ ํ์ผ์ ์ด๋ฆ
+ * @return AmazonS3 Bucket ์ ์ ์ฅ๋ ํ์ผ ๊ฒฝ๋ก url
+ */
+ String getFileUrl(String fileName);
+
+ /**
+ *
+ * @param bucketImageUrl
+ */
+ void deleteFile(String bucketImageUrl);
+
+}
diff --git a/src/main/java/server/uckgisagi/app/image/client/S3FileStorageClient.java b/src/main/java/server/uckgisagi/app/image/client/S3FileStorageClient.java
new file mode 100644
index 00000000..27c9ee55
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/image/client/S3FileStorageClient.java
@@ -0,0 +1,52 @@
+package server.uckgisagi.app.image.client;
+
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.model.CannedAccessControlList;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import server.uckgisagi.common.exception.custom.InternalServerException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+@Component
+@RequiredArgsConstructor
+public class S3FileStorageClient implements FileStorageClient {
+
+ @Value("${cloud.aws.s3.bucket}")
+ private String bucket;
+
+ private final AmazonS3 amazonS3;
+
+ @Override
+ public void uploadFile(MultipartFile file, String fileName) {
+ try (InputStream inputStream = file.getInputStream()) {
+ amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, createObjectMetadata(file))
+ .withCannedAcl(CannedAccessControlList.PublicRead));
+ } catch (IOException e) {
+ throw new InternalServerException(String.format("ํ์ผ (%s) ์
๋ ฅ ์คํธ๋ฆผ์ ๊ฐ์ ธ์ค๋ ์ค ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค", file.getOriginalFilename()));
+ }
+ }
+
+ private ObjectMetadata createObjectMetadata(MultipartFile file) {
+ ObjectMetadata objectMetadata = new ObjectMetadata();
+ objectMetadata.setContentType(file.getContentType());
+ objectMetadata.setContentLength(file.getSize());
+ return objectMetadata;
+ }
+
+ @Override
+ public String getFileUrl(String fileName) {
+ return amazonS3.getUrl(bucket, fileName).toString();
+ }
+
+ @Override // TODO : ์ด๋ ๊ฒ ์ญ์ ํ๋๊ฑฐ ๋ง๋์ง ํ์ธ
+ public void deleteFile(String bucketImageUrl) {
+ amazonS3.deleteObject(bucket, bucketImageUrl);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/image/provider/UploadProvider.java b/src/main/java/server/uckgisagi/app/image/provider/UploadProvider.java
new file mode 100644
index 00000000..1d4a02c6
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/image/provider/UploadProvider.java
@@ -0,0 +1,22 @@
+package server.uckgisagi.app.image.provider;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import server.uckgisagi.app.image.client.S3FileStorageClient;
+import server.uckgisagi.app.image.provider.dto.request.UploadFileRequest;
+
+@Component
+@RequiredArgsConstructor
+public class UploadProvider {
+
+ private final S3FileStorageClient fileStorageClient;
+
+ public String uploadFile(UploadFileRequest request, MultipartFile file) {
+ request.validateAvailableContentType(file.getContentType());
+ String fileName = request.getFileNameWithBucketDirectory(file.getOriginalFilename());
+ fileStorageClient.uploadFile(file, fileName);
+ return fileStorageClient.getFileUrl(fileName);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/image/provider/dto/request/ImageUploadFileRequest.java b/src/main/java/server/uckgisagi/app/image/provider/dto/request/ImageUploadFileRequest.java
new file mode 100644
index 00000000..36bcc00e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/image/provider/dto/request/ImageUploadFileRequest.java
@@ -0,0 +1,25 @@
+package server.uckgisagi.app.image.provider.dto.request;
+
+import lombok.*;
+import server.uckgisagi.common.type.FileType;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class ImageUploadFileRequest implements UploadFileRequest {
+
+ private FileType type;
+
+ public static ImageUploadFileRequest from(FileType type) {
+ return new ImageUploadFileRequest(type);
+ }
+
+// public String getFileNameWithBucketDirectory(String originalFilename) {
+// return type.createUniqueFileNameWithExtension(originalFilename);
+// }
+
+ private static final String SEPARATOR = "/";
+ private static final String IMAGE_CONTENT_TYPE_TYPE = "image";
+
+}
diff --git a/src/main/java/server/uckgisagi/app/image/provider/dto/request/UploadFileRequest.java b/src/main/java/server/uckgisagi/app/image/provider/dto/request/UploadFileRequest.java
new file mode 100644
index 00000000..b6ff8ced
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/image/provider/dto/request/UploadFileRequest.java
@@ -0,0 +1,17 @@
+package server.uckgisagi.app.image.provider.dto.request;
+
+import server.uckgisagi.common.type.FileType;
+
+public interface UploadFileRequest {
+
+ FileType getType();
+
+ default void validateAvailableContentType(String contentType) {
+ getType().validateAvailableContentType(contentType);
+ }
+
+ default String getFileNameWithBucketDirectory(String originalFilename) {
+ return getType().createUniqueFileNameWithExtension(originalFilename);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/NotificationController.java b/src/main/java/server/uckgisagi/app/notification/NotificationController.java
new file mode 100644
index 00000000..e97866cd
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/NotificationController.java
@@ -0,0 +1,39 @@
+package server.uckgisagi.app.notification;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+import server.uckgisagi.app.notification.provider.NotificationServiceProvider;
+import server.uckgisagi.app.notification.service.NotificationService;
+import server.uckgisagi.app.user.domain.dictionary.UserDictionary;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+public class NotificationController {
+
+ private final NotificationServiceProvider notificationServiceProvider;
+ private final UserRepository userRepository;
+
+ @ApiOperation("[์ธ์ฆ] ์น๊ตฌ ํ ํ์ด์ง - ์น๊ตฌ๊ฐ ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์๋ ๊ฒฝ์ฐ '์ฐ๋ฅด๊ธฐ'")
+ @Auth
+ @PostMapping("/v1/notification/{friendUserId}/poke")
+ public ApiSuccessResponse sendPokeNotification(@PathVariable Long friendUserId, @ApiIgnore @LoginUserId Long userId) {
+ NotificationService notificationService = notificationServiceProvider.getNotificationService(NotificationType.POKE);
+ notificationService.sendNotification(userId, friendUserId, UserDictionary.from(List.of(
+ UserServiceUtils.findByUserId(userRepository, userId),
+ UserServiceUtils.findByUserId(userRepository, friendUserId))));
+ return ApiSuccessResponse.success(SuccessResponseResult.CREATED_NOTIFICATION);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/constant/NotificationConstants.java b/src/main/java/server/uckgisagi/app/notification/constant/NotificationConstants.java
new file mode 100644
index 00000000..5f0a5871
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/constant/NotificationConstants.java
@@ -0,0 +1,18 @@
+package server.uckgisagi.app.notification.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NotificationConstants {
+ public static final String POKE_TITLE = "๐จ ํ-! ์ด-๋น์! ๐จ";
+ public static final String POKE_BODY = "%s ๋์ด ํ์๋์ ์ฐ๋ ์ด์! ์ง๊ตฌ๋ฅผ ์ต์ง๋ก ์ฌ๋ํ๊ณ ๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!";
+ public static final String POKE_MESSAGE = "%s ๋์ด ํ์๋์ ์ฐ๋ ์ต๋๋ค";
+
+ public static final String FOLLOW_TITLE = "%s ๋์ด ํ๋ก์ฐํ์ด์!";
+ public static final String FOLLOW_BODY = "%s ๋์ ํผ๋๋ฅผ ํ์ธํด๋ณด์์!";
+ public static final String FOLLOW_MESSAGE = "%s ๋์ด ํ์๋์ ํ๋ก์ฐํ๊ธฐ ์์ํ์ต๋๋ค";
+
+ public static final String FAILURE_MESSAGE = "โ ์๋ฆผ ์ ์ก ์คํจ โ";
+ public static final String SUCCESS_MESSAGE = "โ
์๋ฆผ ์ ์ก ์ฑ๊ณต ๐";
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/domain/entity/Notifications.java b/src/main/java/server/uckgisagi/app/notification/domain/entity/Notifications.java
new file mode 100644
index 00000000..7bb11a96
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/domain/entity/Notifications.java
@@ -0,0 +1,51 @@
+package server.uckgisagi.app.notification.domain.entity;
+
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Notifications extends AuditingTimeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "notification_id", nullable = false)
+ private Long id;
+
+ @Column(nullable = false)
+ private Long userId; // ์๋์ ๋ณด๋ด๋ ์ ์ ์์ด๋
+
+ @Column(nullable = false)
+ private Long targetUserId; // ์๋์ ๋ฐ์ ์ ์ ์์ด๋
+
+ @Column(nullable = false, length = 20)
+ @Enumerated(EnumType.STRING)
+ private NotificationType type;
+
+ @Column(columnDefinition = "TEXT", nullable = false)
+ private String message;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private Notifications(final Long userId, final Long targetUserId, final NotificationType type, final String message) {
+ this.userId = userId;
+ this.targetUserId = targetUserId;
+ this.type = type;
+ this.message = message;
+ }
+
+ public static Notifications newInstance(Long userId, Long targetUserId, NotificationType type, String message) {
+ return Notifications.builder()
+ .userId(userId)
+ .targetUserId(targetUserId)
+ .type(type)
+ .message(message)
+ .build();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/notification/entity/enumerate/NotificationType.java b/src/main/java/server/uckgisagi/app/notification/domain/entity/enumerate/NotificationType.java
similarity index 86%
rename from src/main/java/server/uckgisagi/domain/notification/entity/enumerate/NotificationType.java
rename to src/main/java/server/uckgisagi/app/notification/domain/entity/enumerate/NotificationType.java
index d7da108b..b89f6301 100644
--- a/src/main/java/server/uckgisagi/domain/notification/entity/enumerate/NotificationType.java
+++ b/src/main/java/server/uckgisagi/app/notification/domain/entity/enumerate/NotificationType.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.notification.entity.enumerate;
+package server.uckgisagi.app.notification.domain.entity.enumerate;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -23,5 +23,4 @@ public String getKey() {
public String getValue() {
return value;
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/notification/domain/repository/NotificationRepository.java b/src/main/java/server/uckgisagi/app/notification/domain/repository/NotificationRepository.java
new file mode 100644
index 00000000..f4e05536
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/domain/repository/NotificationRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.notification.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.notification.domain.entity.Notifications;
+
+public interface NotificationRepository extends JpaRepository {
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/provider/NotificationServiceProvider.java b/src/main/java/server/uckgisagi/app/notification/provider/NotificationServiceProvider.java
new file mode 100644
index 00000000..2de3ec59
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/provider/NotificationServiceProvider.java
@@ -0,0 +1,32 @@
+package server.uckgisagi.app.notification.provider;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import server.uckgisagi.app.notification.service.NotificationService;
+import server.uckgisagi.app.notification.service.impl.FollowNotificationService;
+import server.uckgisagi.app.notification.service.impl.PokeNotificationService;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class NotificationServiceProvider {
+
+ private static final Map notificationServiceMap = new HashMap<>();
+
+ private final PokeNotificationService pokeNotificationService;
+ private final FollowNotificationService followNotificationService;
+
+ @PostConstruct
+ public void initNotificationServiceMap() {
+ notificationServiceMap.put(NotificationType.POKE, pokeNotificationService);
+ notificationServiceMap.put(NotificationType.FOLLOW, followNotificationService);
+ }
+
+ public NotificationService getNotificationService(NotificationType notificationType) {
+ return notificationServiceMap.get(notificationType);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/service/NotificationService.java b/src/main/java/server/uckgisagi/app/notification/service/NotificationService.java
new file mode 100644
index 00000000..32547882
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/service/NotificationService.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.notification.service;
+
+import server.uckgisagi.app.user.domain.dictionary.UserDictionary;
+
+public interface NotificationService {
+ void sendNotification(Long userId, Long targetUserId, UserDictionary dictionary);
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/service/SendMessageService.java b/src/main/java/server/uckgisagi/app/notification/service/SendMessageService.java
new file mode 100644
index 00000000..d1c1701e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/service/SendMessageService.java
@@ -0,0 +1,118 @@
+package server.uckgisagi.app.notification.service;
+
+import com.google.api.core.ApiFutureCallback;
+import com.google.api.core.ApiFutures;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.firebase.messaging.Message;
+import com.google.firebase.messaging.Notification;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import server.uckgisagi.app.notification.domain.entity.Notifications;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+import server.uckgisagi.app.notification.domain.repository.NotificationRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.config.firebase.FirebaseInitializer;
+
+import static server.uckgisagi.app.notification.constant.NotificationConstants.*;
+
+public abstract class SendMessageService {
+
+ protected void sendMessageByNotificationType(NotificationRepository notificationRepository, User user, User friend, NotificationType type, Logger log) {
+
+ setCommonFields(notificationRepository, user, friend, log);
+ switch (type) {
+ case POKE:
+ setPokeFields();
+ break;
+ case FOLLOW:
+ setFollowFields();
+ break;
+ }
+ sendAsyncFCM();
+ }
+
+ private void sendAsyncFCM() {
+ ApiFutures.addCallback(
+ FirebaseInitializer.getFirebaseMessaging().sendAsync(message),
+ getApiFutureCallback(),
+ MoreExecutors.directExecutor());
+ }
+
+ @NotNull
+ private ApiFutureCallback getApiFutureCallback() {
+ return new ApiFutureCallback() {
+ @Override
+ public void onFailure(Throwable t) {
+ log.error(FAILURE_MESSAGE);
+ }
+
+ @Override
+ public void onSuccess(String result) {
+ notificationRepository.save(
+ Notifications.newInstance(userId, friendUserId, notificationType, notificationMessage)
+ );
+ log.info(SUCCESS_MESSAGE);
+ }
+ };
+ }
+
+ private Message getPokeMessage() {
+ return Message.builder()
+ .setToken(targetToken)
+ .setNotification(getPokeNotification())
+ .build();
+ }
+
+ private Notification getPokeNotification() {
+ return new Notification(
+ POKE_TITLE,
+ String.format(POKE_BODY, userNickname)
+ );
+ }
+
+ private Message getFollowMessage() {
+ return Message.builder()
+ .setToken(targetToken)
+ .setNotification(getFollowNotification())
+ .build();
+ }
+
+ private Notification getFollowNotification() {
+ return new Notification(
+ String.format(FOLLOW_TITLE, userNickname),
+ String.format(FOLLOW_BODY, userNickname)
+ );
+ }
+
+ private NotificationRepository notificationRepository;
+ private Long userId;
+ private String userNickname;
+ private Long friendUserId;
+ private String targetToken;
+ private Logger log;
+
+ private NotificationType notificationType;
+ private Message message;
+ private String notificationMessage;
+
+ private void setFollowFields() {
+ this.notificationType = NotificationType.FOLLOW;
+ this.message = getFollowMessage();
+ this.notificationMessage = String.format(FOLLOW_MESSAGE, userNickname);
+ }
+
+ private void setPokeFields() {
+ this.notificationType = NotificationType.POKE;
+ this.message = getPokeMessage();
+ this.notificationMessage = String.format(POKE_MESSAGE, userNickname);
+ }
+
+ private void setCommonFields(NotificationRepository notificationRepository, User user, User friend, Logger log) {
+ this.notificationRepository = notificationRepository;
+ this.userId = user.getId();
+ this.userNickname = user.getNickname();
+ this.friendUserId = friend.getId();
+ this.targetToken = friend.getUserFcmToken();
+ this.log = log;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/service/impl/FollowNotificationService.java b/src/main/java/server/uckgisagi/app/notification/service/impl/FollowNotificationService.java
new file mode 100644
index 00000000..0b88dfa9
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/service/impl/FollowNotificationService.java
@@ -0,0 +1,32 @@
+package server.uckgisagi.app.notification.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.notification.service.NotificationService;
+import server.uckgisagi.app.notification.service.SendMessageService;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+import server.uckgisagi.app.notification.domain.repository.NotificationRepository;
+import server.uckgisagi.app.user.domain.dictionary.UserDictionary;
+
+@Service
+@RequiredArgsConstructor
+public class FollowNotificationService extends SendMessageService implements NotificationService {
+
+ private static final Logger log = LoggerFactory.getLogger(FollowNotificationService.class);
+
+ private final NotificationRepository notificationRepository;
+
+ @Override
+ @Transactional
+ public void sendNotification(Long userId, Long targetUserId, UserDictionary dictionary) {
+ sendMessageByNotificationType(
+ notificationRepository,
+ dictionary.getUserByUserId(userId),
+ dictionary.getUserByUserId(targetUserId),
+ NotificationType.FOLLOW, log
+ );
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/notification/service/impl/PokeNotificationService.java b/src/main/java/server/uckgisagi/app/notification/service/impl/PokeNotificationService.java
new file mode 100644
index 00000000..2495fb4b
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/notification/service/impl/PokeNotificationService.java
@@ -0,0 +1,32 @@
+package server.uckgisagi.app.notification.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.notification.service.NotificationService;
+import server.uckgisagi.app.notification.service.SendMessageService;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+import server.uckgisagi.app.notification.domain.repository.NotificationRepository;
+import server.uckgisagi.app.user.domain.dictionary.UserDictionary;
+
+@Service
+@RequiredArgsConstructor
+public class PokeNotificationService extends SendMessageService implements NotificationService {
+
+ private static final Logger log = LoggerFactory.getLogger(PokeNotificationService.class);
+
+ private final NotificationRepository notificationRepository;
+
+ @Override
+ @Transactional
+ public void sendNotification(Long userId, Long targetUserId, UserDictionary dictionary) {
+ sendMessageByNotificationType(
+ notificationRepository,
+ dictionary.getUserByUserId(userId),
+ dictionary.getUserByUserId(targetUserId),
+ NotificationType.POKE, log
+ );
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/controller/PostController.java b/src/main/java/server/uckgisagi/app/post/controller/PostController.java
new file mode 100644
index 00000000..0b1a9490
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/controller/PostController.java
@@ -0,0 +1,43 @@
+package server.uckgisagi.app.post.controller;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import server.uckgisagi.app.post.dto.request.AddPostRequest;
+import server.uckgisagi.app.post.dto.response.GradeResponse;
+import server.uckgisagi.app.post.service.PostService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import javax.validation.Valid;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class PostController {
+
+ private final PostService postService;
+
+ @ApiOperation("[์ธ์ฆ] ์ฑ๋ฆฐ์ง ์์ฑ ํ์ด์ง - ์ฑ๋ฆฐ์ง ๊ธ ์์ฑํ๊ธฐ")
+ @Auth
+ @PostMapping("/v1/post")
+ public ApiSuccessResponse addPostWithImage(@Valid AddPostRequest request,
+ @RequestPart MultipartFile imageFile,
+ @ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(CREATED_CERTIFICATION_POST, postService.addPostWithImage(request, imageFile, userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์ฑ๋ฆฐ์ง ๊ธ ์ญ์ ํ์ด์ง - ์ฑ๋ฆฐ์ง ๊ธ ์ญ์ ํ๊ธฐ")
+ @Auth
+ @DeleteMapping("/v1/post/delete")
+ public ApiSuccessResponse deletePost(@RequestParam Long postId, @ApiIgnore @LoginUserId Long userId) {
+ postService.deletePost(postId, userId);
+ return ApiSuccessResponse.success(NO_CONTENT_DELETE_POST);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/post/controller/PostRetrieveController.java b/src/main/java/server/uckgisagi/app/post/controller/PostRetrieveController.java
new file mode 100644
index 00000000..e835701d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/controller/PostRetrieveController.java
@@ -0,0 +1,54 @@
+package server.uckgisagi.app.post.controller;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+import server.uckgisagi.app.post.dto.response.DetailPostResponse;
+import server.uckgisagi.app.post.dto.response.PreviewPostResponse;
+import server.uckgisagi.app.post.service.PostRetrieveService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class PostRetrieveController {
+
+ private final PostRetrieveService postRetrieveService;
+
+ @ApiOperation("[์ธ์ฆ] ๋๋ฌ๋ณด๊ธฐ ํ์ด์ง - ๋ชจ๋ ์ ์ ์ ์ฑ๋ฆฐ์ง ๊ธ ์กฐํํ๊ธฐ")
+ @Auth
+ @GetMapping("/v1/post/all")
+ public ApiSuccessResponse> retrieveAllPost(@ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_ALL_POST, postRetrieveService.retrieveAllPost(userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ๋๋ฌ๋ณด๊ธฐ ํ์ด์ง - ์ฑ๋ฆฐ์ง ๊ธ ์์ธ๋ณด๊ธฐ")
+ @Auth
+ @GetMapping("/v1/post/{postId}")
+ public ApiSuccessResponse retrieveDetailPost(@PathVariable Long postId, @ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_POST, postRetrieveService.retrieveDetailPost(postId, userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์คํฌ๋ฉ ํ์ด์ง - ์ ์ ๊ฐ ์คํฌ๋ฉํ ์ฑ๋ฆฐ์ง ๊ธ ์กฐํํ๊ธฐ")
+ @Auth
+ @GetMapping("/v1/post/scrap")
+ public ApiSuccessResponse> retrieveScrapPost(@ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_MY_SCRAP_POST, postRetrieveService.retrieveScrapPost(userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์คํฌ๋ฉ ํ์ด์ง - ์ ์ ๊ฐ ์คํฌ๋ฉํ ์ฑ๋ฆฐ์ง ๊ธ ์์ธ๋ณด๊ธฐ")
+ @Auth
+ @GetMapping("/v1/post/scrap/{postId}")
+ public ApiSuccessResponse retrieveDetailScrapPost(@PathVariable Long postId, @ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_MY_SCRAP_POST_DETAIL, postRetrieveService.retrieveDetailScrapPost(postId, userId));
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/post/domain/entity/Post.java b/src/main/java/server/uckgisagi/app/post/domain/entity/Post.java
new file mode 100644
index 00000000..c9df38f5
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/domain/entity/Post.java
@@ -0,0 +1,61 @@
+package server.uckgisagi.app.post.domain.entity;
+
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import server.uckgisagi.app.post.domain.entity.enumerate.PostStatus;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Post extends AuditingTimeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "post_id", nullable = false)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id")
+ private User user;
+
+ @Column(nullable = false)
+ private String imageUrl;
+
+ @Column(nullable = false)
+ private String title;
+
+ @Column(nullable = false)
+ private String content;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, length = 10)
+ private PostStatus postStatus;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private Post(final User user, final String imageUrl, final String title, final String content) {
+ this.user = user;
+ this.imageUrl = imageUrl;
+ this.title = title;
+ this.content = content;
+ this.postStatus = PostStatus.ACTIVE;
+ }
+
+ public static Post newInstance(User user, String imageUrl, String title, String content) {
+ return Post.builder()
+ .user(user)
+ .imageUrl(imageUrl)
+ .title(title)
+ .content(content)
+ .build();
+ }
+
+ public void changeStatus(PostStatus postStatus){
+ this.postStatus = postStatus;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/domain/entity/enumerate/PostStatus.java b/src/main/java/server/uckgisagi/app/post/domain/entity/enumerate/PostStatus.java
new file mode 100644
index 00000000..065693a0
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/domain/entity/enumerate/PostStatus.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.post.domain.entity.enumerate;
+
+public enum PostStatus {
+ ACTIVE,
+ INACTIVE
+ ;
+}
diff --git a/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepository.java b/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepository.java
new file mode 100644
index 00000000..27ff8f26
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.post.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.post.domain.entity.Post;
+
+public interface PostRepository extends JpaRepository, PostRepositoryCustom {
+}
diff --git a/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepositoryCustom.java b/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepositoryCustom.java
new file mode 100644
index 00000000..258184a8
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepositoryCustom.java
@@ -0,0 +1,17 @@
+package server.uckgisagi.app.post.domain.repository;
+
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.time.LocalDate;
+import java.util.List;
+
+public interface PostRepositoryCustom {
+ Post findPostByPostId(Long postId);
+ List findPostByUserId(Long userId);
+ boolean existsByTodayDate(LocalDate today, Long userId);
+ List findUserIdsByTodayDate(LocalDate today, List userIds);
+ Post findByPostIdAndUserId(Long postId, Long userId);
+ List findAllByPostStatus(List blockUserIds);
+ List findPostByDateAndUserId(LocalDate date, Long userId);
+}
diff --git a/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepositoryCustomImpl.java
new file mode 100644
index 00000000..eb04c1ff
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/domain/repository/PostRepositoryCustomImpl.java
@@ -0,0 +1,99 @@
+package server.uckgisagi.app.post.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.post.domain.entity.enumerate.PostStatus;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import static server.uckgisagi.app.post.domain.entity.QPost.*;
+
+@RequiredArgsConstructor
+public class PostRepositoryCustomImpl implements PostRepositoryCustom {
+
+ private final JPAQueryFactory query;
+
+ private static final long ONE_DAY = 1L;
+
+ @Override
+ public Post findPostByPostId(Long postId) {
+ return query
+ .selectFrom(post)
+ .where(post.id.eq(postId))
+ .fetchFirst();
+ }
+
+ @Override
+ public List findPostByUserId(Long userId) {
+ return query
+ .selectFrom(post)
+ .where(post.user.id.eq(userId))
+ .fetch();
+ }
+
+ @Override
+ public boolean existsByTodayDate(LocalDate today, Long userId) {
+ return query
+ .selectOne()
+ .from(post)
+ .where(
+ post.user.id.eq(userId),
+ post.createdAt.between(
+ today.atStartOfDay(),
+ today.atStartOfDay().plusDays(ONE_DAY)
+ ))
+ .fetchFirst() != null;
+ }
+
+
+ @Override
+ public List findUserIdsByTodayDate(LocalDate today, List userIds) {
+ return query
+ .select(post.user)
+ .from(post)
+ .where(
+ post.user.id.in(userIds),
+ post.createdAt.between(
+ today.atStartOfDay(),
+ today.atStartOfDay().plusDays(ONE_DAY)
+ ))
+ .fetch();
+ }
+
+ @Override
+ public Post findByPostIdAndUserId(Long postId, Long userId){
+ return query
+ .selectFrom(post)
+ .where(
+ post.id.eq(postId),
+ post.user.id.eq(userId)
+ )
+ .fetchOne();
+ }
+
+ @Override
+ public List findAllByPostStatus(List blockUserIds) {
+ return query.selectFrom(post)
+ .where(
+ post.postStatus.eq(PostStatus.ACTIVE),
+ post.user.id.notIn(blockUserIds)
+ )
+ .orderBy(post.createdAt.desc())
+ .fetch();
+ }
+
+ public List findPostByDateAndUserId(LocalDate date, Long userId) {
+ return query
+ .selectFrom(post)
+ .where(
+ post.user.id.eq(userId),
+ post.createdAt.between(
+ date.atStartOfDay(),
+ date.atStartOfDay().plusDays(ONE_DAY)
+ ))
+ .fetch();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/dto/request/AddPostRequest.java b/src/main/java/server/uckgisagi/app/post/dto/request/AddPostRequest.java
new file mode 100644
index 00000000..fb13aa30
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/dto/request/AddPostRequest.java
@@ -0,0 +1,24 @@
+package server.uckgisagi.app.post.dto.request;
+
+import lombok.*;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import javax.validation.constraints.NotBlank;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class AddPostRequest {
+
+ @NotBlank
+ private String title;
+
+ @NotBlank
+ private String content;
+
+ public Post toPostEntity(User user, String imageUrl) {
+ return Post.newInstance(user, imageUrl, title, content);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/dto/response/DetailPostResponse.java b/src/main/java/server/uckgisagi/app/post/dto/response/DetailPostResponse.java
new file mode 100644
index 00000000..3f5bbe88
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/dto/response/DetailPostResponse.java
@@ -0,0 +1,42 @@
+package server.uckgisagi.app.post.dto.response;
+
+import lombok.*;
+import server.uckgisagi.common.dto.AuditingTimeResponse;
+import server.uckgisagi.app.post.domain.entity.Post;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DetailPostResponse extends AuditingTimeResponse {
+
+ private Long postId;
+ private Long userId;
+ private String nickname;
+ private String imageUrl;
+ private String content;
+ private ScrapStatus scrapStatus;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private DetailPostResponse(final Long postId, final Long userId, final String nickname, final String imageUrl,
+ final String content, final ScrapStatus scrapStatus) {
+ this.postId = postId;
+ this.userId = userId;
+ this.nickname = nickname;
+ this.imageUrl = imageUrl;
+ this.content = content;
+ this.scrapStatus = scrapStatus;
+ }
+
+ public static DetailPostResponse of(Post post, ScrapStatus scrapStatus) {
+ DetailPostResponse response = DetailPostResponse.builder()
+ .postId(post.getId())
+ .userId(post.getUser().getId())
+ .nickname(post.getUser().getNickname())
+ .imageUrl(post.getImageUrl())
+ .content(post.getContent())
+ .scrapStatus(scrapStatus)
+ .build();
+ response.setBaseTime(post);
+ return response;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/dto/response/GradeResponse.java b/src/main/java/server/uckgisagi/app/post/dto/response/GradeResponse.java
new file mode 100644
index 00000000..0729fe3e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/dto/response/GradeResponse.java
@@ -0,0 +1,23 @@
+package server.uckgisagi.app.post.dto.response;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import server.uckgisagi.app.user.domain.entity.enumerate.UserGrade;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class GradeResponse {
+
+ private UserGrade grade;
+
+ private GradeResponse(UserGrade grade) {
+ this.grade = grade;
+ }
+
+ public static GradeResponse from(UserGrade grade) {
+ return new GradeResponse(grade);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/dto/response/PostResponse.java b/src/main/java/server/uckgisagi/app/post/dto/response/PostResponse.java
new file mode 100644
index 00000000..0d9433fe
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/dto/response/PostResponse.java
@@ -0,0 +1,35 @@
+package server.uckgisagi.app.post.dto.response;
+
+import lombok.*;
+import server.uckgisagi.common.dto.AuditingTimeResponse;
+import server.uckgisagi.app.post.domain.entity.Post;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class PostResponse extends AuditingTimeResponse {
+
+ private Long postId;
+ private String imageUrl;
+ private String title;
+ private String content;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private PostResponse(final Long postId, final String imageUrl, final String title, final String content) {
+ this.postId = postId;
+ this.imageUrl = imageUrl;
+ this.title = title;
+ this.content = content;
+ }
+
+ public static PostResponse from(Post post) {
+ PostResponse response = PostResponse.builder()
+ .postId(post.getId())
+ .imageUrl(post.getImageUrl())
+ .title(post.getTitle())
+ .content(post.getContent())
+ .build();
+ response.setBaseTime(post);
+ return response;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/dto/response/PreviewPostResponse.java b/src/main/java/server/uckgisagi/app/post/dto/response/PreviewPostResponse.java
new file mode 100644
index 00000000..8dcfee22
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/dto/response/PreviewPostResponse.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.app.post.dto.response;
+
+import lombok.*;
+import server.uckgisagi.app.post.domain.entity.Post;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class PreviewPostResponse {
+
+ private Long postId;
+ private String imageUrl;
+ private String content;
+ private ScrapStatus scrapStatus;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private PreviewPostResponse(final Long postId, final String imageUrl,
+ final String content, final ScrapStatus scrapStatus) {
+ this.postId = postId;
+ this.imageUrl = imageUrl;
+ this.content = content;
+ this.scrapStatus = scrapStatus;
+ }
+
+ public static PreviewPostResponse of(Post post, ScrapStatus scrapStatus) {
+ return PreviewPostResponse.builder()
+ .postId(post.getId())
+ .imageUrl(post.getImageUrl())
+ .content(post.getContent())
+ .scrapStatus(scrapStatus)
+ .build();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/dto/response/ScrapStatus.java b/src/main/java/server/uckgisagi/app/post/dto/response/ScrapStatus.java
new file mode 100644
index 00000000..c431ff38
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/dto/response/ScrapStatus.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.post.dto.response;
+
+public enum ScrapStatus {
+ ACTIVE,
+ INACTIVE,
+ ;
+}
diff --git a/src/main/java/server/uckgisagi/app/post/service/PostRetrieveService.java b/src/main/java/server/uckgisagi/app/post/service/PostRetrieveService.java
new file mode 100644
index 00000000..f7267020
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/service/PostRetrieveService.java
@@ -0,0 +1,70 @@
+package server.uckgisagi.app.post.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.post.dto.response.DetailPostResponse;
+import server.uckgisagi.app.post.dto.response.PreviewPostResponse;
+import server.uckgisagi.app.post.dto.response.ScrapStatus;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.app.block.domain.entity.Block;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.scrap.domain.repository.ScrapRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class PostRetrieveService {
+
+ private final UserRepository userRepository;
+ private final PostRepository postRepository;
+ private final ScrapRepository scrapRepository;
+
+ public List retrieveAllPost(Long userId) {
+ User me = userRepository.findUserByUserId(userId);
+ List blockUserIds = me.getBlocks().stream()
+ .map(Block::getBlockUserId)
+ .collect(Collectors.toList());
+ List scrapedPosts = scrapRepository.findScrapPostByUserId(userId, blockUserIds);
+
+ return postRepository
+ .findAllByPostStatus(blockUserIds).stream()
+ .map(post -> scrapedPosts.contains(post)
+ ? PreviewPostResponse.of(post, ScrapStatus.ACTIVE)
+ : PreviewPostResponse.of(post, ScrapStatus.INACTIVE))
+ .collect(Collectors.toList());
+ }
+
+ public List retrieveScrapPost(Long userId) {
+ List blockUserIds = userRepository.findUserByUserId(userId)
+ .getBlocks().stream()
+ .map(Block::getBlockUserId)
+ .collect(Collectors.toList());
+
+ return scrapRepository.findScrapPostByUserId(userId, blockUserIds).stream()
+ .map(post -> PreviewPostResponse.of(post, ScrapStatus.ACTIVE))
+ .collect(Collectors.toList());
+ }
+
+ public DetailPostResponse retrieveDetailPost(Long postId, Long userId) {
+// UserServiceUtils.validateExistUser(userRepository, userId); // TODO ๊ณผ์ฐ ๋ชจ๋ api ๋ง๋ค ์ ์ ์ฒดํฌ๋ฅผ ํด์ผํ๋๊ฐ?
+ Post post = PostServiceUtils.findByPostId(postRepository, postId);
+ return scrapRepository.existsByPostAndUserId(post, userId)
+ ? DetailPostResponse.of(post, ScrapStatus.ACTIVE)
+ : DetailPostResponse.of(post, ScrapStatus.INACTIVE);
+ }
+
+ public DetailPostResponse retrieveDetailScrapPost(Long postId, Long userId) {
+// UserServiceUtils.validateExistUser(userRepository, userId);
+ return DetailPostResponse.of(
+ PostServiceUtils.findByPostId(postRepository, postId),
+ ScrapStatus.ACTIVE
+ );
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/service/PostService.java b/src/main/java/server/uckgisagi/app/post/service/PostService.java
new file mode 100644
index 00000000..0445860d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/service/PostService.java
@@ -0,0 +1,37 @@
+package server.uckgisagi.app.post.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import server.uckgisagi.app.image.provider.UploadProvider;
+import server.uckgisagi.app.image.provider.dto.request.ImageUploadFileRequest;
+import server.uckgisagi.app.post.dto.request.AddPostRequest;
+import server.uckgisagi.app.post.dto.response.GradeResponse;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.common.type.FileType;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+@Service
+@RequiredArgsConstructor
+public class PostService {
+
+ private final PostRepository postRepository;
+ private final UserRepository userRepository;
+ private final UploadProvider uploadProvider;
+
+ @Transactional
+ public GradeResponse addPostWithImage(AddPostRequest request, MultipartFile imageFile, Long userId) {
+ String imageUrl = uploadProvider.uploadFile(ImageUploadFileRequest.from(FileType.POST_IMAGE), imageFile);
+ User user = UserServiceUtils.findByUserId(userRepository, userId);
+ user.addPosts(postRepository.save(request.toPostEntity(user, imageUrl)));
+ return GradeResponse.from(user.getGrade());
+ }
+
+ @Transactional
+ public void deletePost(Long postId, Long userId) {
+ postRepository.delete(PostServiceUtils.findByPostIdAndUserId(postRepository, postId, userId));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/post/service/PostServiceUtils.java b/src/main/java/server/uckgisagi/app/post/service/PostServiceUtils.java
new file mode 100644
index 00000000..20bb8c78
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/post/service/PostServiceUtils.java
@@ -0,0 +1,29 @@
+package server.uckgisagi.app.post.service;
+
+import org.jetbrains.annotations.NotNull;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+public class PostServiceUtils {
+
+ @NotNull
+ public static Post findByPostId(PostRepository postRepository, Long postId) {
+ Post post = postRepository.findPostByPostId(postId);
+ if (post == null) {
+ throw new NotFoundException(String.format("์กด์ฌํ์ง ์๋ ๊ฒ์๊ธ (%s) ์
๋๋ค", postId), NOT_FOUND_POST_EXCEPTION);
+ }
+ return post;
+ }
+
+ @NotNull
+ public static Post findByPostIdAndUserId(PostRepository postRepository, Long postId, Long userId) {
+ Post post = postRepository.findByPostIdAndUserId(postId, userId);
+ if (post == null) {
+ throw new NotFoundException(String.format("์กด์ฌํ์ง ์๋ ๊ฒ์๊ธ (%s) ์
๋๋ค", postId), NOT_FOUND_POST_EXCEPTION);
+ }
+ return post;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/review/ReviewController.java b/src/main/java/server/uckgisagi/app/review/ReviewController.java
new file mode 100644
index 00000000..c65a9d3d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/review/ReviewController.java
@@ -0,0 +1,40 @@
+package server.uckgisagi.app.review;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import server.uckgisagi.app.review.dto.request.AddReviewRequest;
+import server.uckgisagi.app.review.dto.response.ReviewResponse;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import javax.validation.Valid;
+import java.util.List;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class ReviewController {
+
+ private final ReviewService reviewService;
+
+ @ApiOperation("[์ธ์ฆ] ๋ฆฌํ ์คํ
์ด์
์์ธ๋ณด๊ธฐ ํ์ด์ง - ํ๊ธฐ ๋ฑ๋ก")
+ @Auth
+ @PostMapping("/v1/review")
+ public ApiSuccessResponse addReview(@Valid @RequestBody AddReviewRequest request, @ApiIgnore @LoginUserId Long userId) {
+ reviewService.addReview(request, userId);
+ return ApiSuccessResponse.success(CREATED_REVIEW_COMMENT);
+ }
+
+ @ApiOperation("[์ธ์ฆ] ๋ฆฌํ ์คํ
์ด์
์์ธ๋ณด๊ธฐ ํ์ด์ง - ํ๊ธฐ ์กฐํ")
+ @Auth
+ @GetMapping("/v1/review/{storeId}")
+ public ApiSuccessResponse> retrieveStoreReview(@PathVariable Long storeId) {
+ return ApiSuccessResponse.success(OK_RETRIEVE_STORE_REVIEW, reviewService.retrieveReview(storeId));
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/review/ReviewService.java b/src/main/java/server/uckgisagi/app/review/ReviewService.java
new file mode 100644
index 00000000..f1db6fb8
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/review/ReviewService.java
@@ -0,0 +1,40 @@
+package server.uckgisagi.app.review;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.review.dto.request.AddReviewRequest;
+import server.uckgisagi.app.review.dto.response.ReviewResponse;
+import server.uckgisagi.app.store.service.StoreServiceUtils;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.app.review.domain.repository.ReviewRepository;
+import server.uckgisagi.app.store.domain.repository.StoreRepository;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class ReviewService {
+
+ private final ReviewRepository reviewRepository;
+ private final StoreRepository storeRepository;
+ private final UserRepository userRepository;
+
+ @Transactional
+ public void addReview(AddReviewRequest request, Long userId) {
+ reviewRepository.save(request.toReviewEntity(
+ StoreServiceUtils.findByStoreId(storeRepository, request.getStoreId()),
+ UserServiceUtils.findByUserId(userRepository, userId)
+ ));
+ }
+
+ @Transactional(readOnly = true)
+ public List retrieveReview(Long storeId) {
+ return StoreServiceUtils.findByStoreId(storeRepository, storeId)
+ .getReviews().stream()
+ .map(ReviewResponse::from)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/review/entity/Review.java b/src/main/java/server/uckgisagi/app/review/domain/entity/Review.java
similarity index 77%
rename from src/main/java/server/uckgisagi/domain/review/entity/Review.java
rename to src/main/java/server/uckgisagi/app/review/domain/entity/Review.java
index 3bc22b33..1ae58034 100644
--- a/src/main/java/server/uckgisagi/domain/review/entity/Review.java
+++ b/src/main/java/server/uckgisagi/app/review/domain/entity/Review.java
@@ -1,10 +1,10 @@
-package server.uckgisagi.domain.review.entity;
+package server.uckgisagi.app.review.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.store.entity.Store;
-import server.uckgisagi.domain.user.entity.User;
+import server.uckgisagi.app.store.domain.entity.Store;
+import server.uckgisagi.app.user.domain.entity.User;
import javax.persistence.*;
@@ -35,8 +35,7 @@ private Review(final Store store, final User user, final String comment) {
this.comment = comment;
}
- public static Review of(Store store, User user, String comment) {
+ public static Review newInstance(Store store, User user, String comment) {
return new Review(store, user, comment);
}
-
}
diff --git a/src/main/java/server/uckgisagi/domain/review/repository/ReviewRepository.java b/src/main/java/server/uckgisagi/app/review/domain/repository/ReviewRepository.java
similarity index 55%
rename from src/main/java/server/uckgisagi/domain/review/repository/ReviewRepository.java
rename to src/main/java/server/uckgisagi/app/review/domain/repository/ReviewRepository.java
index e0590de3..8e956c33 100644
--- a/src/main/java/server/uckgisagi/domain/review/repository/ReviewRepository.java
+++ b/src/main/java/server/uckgisagi/app/review/domain/repository/ReviewRepository.java
@@ -1,7 +1,7 @@
-package server.uckgisagi.domain.review.repository;
+package server.uckgisagi.app.review.domain.repository;
import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.review.entity.Review;
+import server.uckgisagi.app.review.domain.entity.Review;
public interface ReviewRepository extends JpaRepository {
}
diff --git a/src/main/java/server/uckgisagi/app/review/dto/request/AddReviewRequest.java b/src/main/java/server/uckgisagi/app/review/dto/request/AddReviewRequest.java
new file mode 100644
index 00000000..cdcaa3db
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/review/dto/request/AddReviewRequest.java
@@ -0,0 +1,29 @@
+package server.uckgisagi.app.review.dto.request;
+
+import lombok.*;
+import server.uckgisagi.app.review.domain.entity.Review;
+import server.uckgisagi.app.store.domain.entity.Store;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(builderMethodName = "testBuilder")
+public class AddReviewRequest {
+
+ @NotNull
+ private Long storeId;
+
+ @NotBlank
+ private String content;
+
+ public Review toReviewEntity(Store store, User user) {
+ Review review = Review.newInstance(store, user, content);
+ store.addReview(review);
+ return review;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/review/dto/response/ReviewResponse.java b/src/main/java/server/uckgisagi/app/review/dto/response/ReviewResponse.java
new file mode 100644
index 00000000..e47703e3
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/review/dto/response/ReviewResponse.java
@@ -0,0 +1,29 @@
+package server.uckgisagi.app.review.dto.response;
+
+import lombok.*;
+import server.uckgisagi.app.review.domain.entity.Review;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReviewResponse {
+
+ private Long reviewId;
+ private String nickname;
+ private String comment;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private ReviewResponse(final Long reviewId, final String nickname, final String comment) {
+ this.reviewId = reviewId;
+ this.nickname = nickname;
+ this.comment = comment;
+ }
+
+ public static ReviewResponse from(Review review) {
+ return ReviewResponse.builder()
+ .reviewId(review.getId())
+ .nickname(review.getUser().getNickname())
+ .comment(review.getComment())
+ .build();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/scrap/ScrapController.java b/src/main/java/server/uckgisagi/app/scrap/ScrapController.java
new file mode 100644
index 00000000..cdeac46f
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/scrap/ScrapController.java
@@ -0,0 +1,36 @@
+package server.uckgisagi.app.scrap;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import server.uckgisagi.app.scrap.service.ScrapService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.*;
+
+@RestController
+@RequiredArgsConstructor
+public class ScrapController {
+
+ private final ScrapService scrapService;
+
+ @ApiOperation("[์ธ์ฆ] ์คํฌ๋ฉ์ด ๊ฐ๋ฅํ ๋ชจ๋ ํ์ด์ง - ์คํฌ๋ฉ ํ๊ธฐ")
+ @Auth
+ @PostMapping("/v1/scrap/{postId}")
+ public ApiSuccessResponse addScrap(@PathVariable Long postId, @ApiIgnore @LoginUserId Long userId) {
+ scrapService.addScrap(postId, userId);
+ return ApiSuccessResponse.success(NO_CONTENT_SCRAP_POST);
+ }
+
+ @ApiOperation("[์ธ์ฆ] ์คํฌ๋ฉ์ด ๊ฐ๋ฅํ ๋ชจ๋ ํ์ด์ง - ์คํฌ๋ฉ ์ทจ์ ํ๊ธฐ")
+ @Auth
+ @DeleteMapping("/v1/scrap/{postId}")
+ public ApiSuccessResponse deleteScrap(@PathVariable Long postId, @ApiIgnore @LoginUserId Long userId) {
+ scrapService.deleteScrap(postId, userId);
+ return ApiSuccessResponse.success(NO_CONTENT_CANCEL_SCRAP_POST);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/postscrap/entity/PostScrap.java b/src/main/java/server/uckgisagi/app/scrap/domain/entity/Scrap.java
similarity index 55%
rename from src/main/java/server/uckgisagi/domain/postscrap/entity/PostScrap.java
rename to src/main/java/server/uckgisagi/app/scrap/domain/entity/Scrap.java
index e29b5ea3..5b7b3bbe 100644
--- a/src/main/java/server/uckgisagi/domain/postscrap/entity/PostScrap.java
+++ b/src/main/java/server/uckgisagi/app/scrap/domain/entity/Scrap.java
@@ -1,18 +1,18 @@
-package server.uckgisagi.domain.postscrap.entity;
+package server.uckgisagi.app.scrap.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
-import server.uckgisagi.domain.post.entity.Post;
-import server.uckgisagi.domain.user.entity.User;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.user.domain.entity.User;
import javax.persistence.*;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class PostScrap extends AuditingTimeEntity {
+public class Scrap extends AuditingTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -27,4 +27,12 @@ public class PostScrap extends AuditingTimeEntity {
@JoinColumn(name = "post_id")
private Post post; // ์คํฌ๋ฉํ ๊ฒ์๋ฌผ ์์ด๋
+ private Scrap(final User user, final Post post) {
+ this.user = user;
+ this.post = post;
+ }
+
+ public static Scrap newInstance(User user, Post post) {
+ return new Scrap(user, post);
+ }
}
diff --git a/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepository.java b/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepository.java
new file mode 100644
index 00000000..450064ef
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.scrap.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.scrap.domain.entity.Scrap;
+
+public interface ScrapRepository extends JpaRepository, ScrapRepositoryCustom {
+}
diff --git a/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepositoryCustom.java b/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepositoryCustom.java
new file mode 100644
index 00000000..baacad7e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepositoryCustom.java
@@ -0,0 +1,12 @@
+package server.uckgisagi.app.scrap.domain.repository;
+
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.scrap.domain.entity.Scrap;
+
+import java.util.List;
+
+public interface ScrapRepositoryCustom {
+ List findScrapPostByUserId(Long userId, List blockUserIds);
+ boolean existsByPostAndUserId(Post post, Long userId);
+ Scrap findScrapByPostIdAndUserId(Long postId, Long userId);
+}
diff --git a/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepositoryCustomImpl.java
new file mode 100644
index 00000000..e8fc921e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/scrap/domain/repository/ScrapRepositoryCustomImpl.java
@@ -0,0 +1,52 @@
+package server.uckgisagi.app.scrap.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.entity.enumerate.PostStatus;
+import server.uckgisagi.app.scrap.domain.entity.Scrap;
+
+import java.util.List;
+
+import static server.uckgisagi.app.scrap.domain.entity.QScrap.*;
+
+@RequiredArgsConstructor
+public class ScrapRepositoryCustomImpl implements ScrapRepositoryCustom {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public List findScrapPostByUserId(Long userId, List blockUserIds) {
+ return query
+ .select(scrap.post).distinct()
+ .from(scrap)
+ .where(
+ scrap.post.postStatus.eq(PostStatus.ACTIVE),
+ scrap.post.user.id.notIn(blockUserIds),
+ scrap.user.id.eq(userId)
+ )
+ .fetch();
+ }
+
+ @Override
+ public boolean existsByPostAndUserId(Post post, Long userId) {
+ return query
+ .selectOne()
+ .from(scrap)
+ .where(
+ scrap.post.eq(post),
+ scrap.user.id.eq(userId),
+ scrap.post.postStatus.eq(PostStatus.ACTIVE)
+ ).fetchFirst() != null;
+ }
+
+ @Override
+ public Scrap findScrapByPostIdAndUserId(Long postId, Long userId) {
+ return query
+ .selectFrom(scrap)
+ .where(
+ scrap.user.id.eq(userId),
+ scrap.post.id.eq(postId)
+ ).fetchOne();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/scrap/service/ScrapService.java b/src/main/java/server/uckgisagi/app/scrap/service/ScrapService.java
new file mode 100644
index 00000000..2c100339
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/scrap/service/ScrapService.java
@@ -0,0 +1,36 @@
+package server.uckgisagi.app.scrap.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.post.service.PostServiceUtils;
+import server.uckgisagi.app.user.service.UserServiceUtils;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.scrap.domain.entity.Scrap;
+import server.uckgisagi.app.scrap.domain.repository.ScrapRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+@Service
+@RequiredArgsConstructor
+public class ScrapService {
+
+ private final ScrapRepository scrapRepository;
+ private final UserRepository userRepository;
+ private final PostRepository postRepository;
+
+ @Transactional
+ public void addScrap(Long postId, Long userId) {
+ User user = UserServiceUtils.findByUserId(userRepository, userId);
+ Post post = PostServiceUtils.findByPostId(postRepository, postId);
+ if (!scrapRepository.existsByPostAndUserId(post, userId)) {
+ scrapRepository.save(Scrap.newInstance(user, post));
+ }
+ }
+
+ @Transactional
+ public void deleteScrap(Long postId, Long userId) {
+ scrapRepository.delete(ScrapServiceUtils.findByPostIdAndUserId(scrapRepository, postId, userId));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/scrap/service/ScrapServiceUtils.java b/src/main/java/server/uckgisagi/app/scrap/service/ScrapServiceUtils.java
new file mode 100644
index 00000000..3a58d41c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/scrap/service/ScrapServiceUtils.java
@@ -0,0 +1,20 @@
+package server.uckgisagi.app.scrap.service;
+
+import org.jetbrains.annotations.NotNull;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.scrap.domain.entity.Scrap;
+import server.uckgisagi.app.scrap.domain.repository.ScrapRepository;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+public class ScrapServiceUtils {
+
+ @NotNull
+ public static Scrap findByPostIdAndUserId(ScrapRepository scrapRepository, Long postId, Long userId) {
+ Scrap scrap = scrapRepository.findScrapByPostIdAndUserId(postId, userId);
+ if (scrap == null) {
+ throw new NotFoundException(String.format("์คํฌ๋ฉ๋์ง ์์ ๊ธ (%s) ์
๋๋ค", postId), NOT_FOUND_SCRAP_EXCEPTION);
+ }
+ return scrap;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/store/StoreController.java b/src/main/java/server/uckgisagi/app/store/StoreController.java
index 6347b0c8..b25a9f55 100644
--- a/src/main/java/server/uckgisagi/app/store/StoreController.java
+++ b/src/main/java/server/uckgisagi/app/store/StoreController.java
@@ -1,5 +1,6 @@
package server.uckgisagi.app.store;
+import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -8,6 +9,7 @@
import server.uckgisagi.app.store.dto.response.OneStoreResponse;
import server.uckgisagi.app.store.service.StoreRetrieveService;
import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.config.interceptor.Auth;
import static server.uckgisagi.common.success.SuccessResponseResult.*;
@@ -17,11 +19,15 @@ public class StoreController {
private final StoreRetrieveService storeRetrieveService;
+ @ApiOperation("[์ธ์ฆ] ์ค์ฒ์ฅ์ ๋ณด๊ธฐ ํ์ด์ง - ๋ชจ๋ ๋ฆฌํ์คํ
์ด์
์ ๋ณด ์กฐํ")
+ @Auth
@GetMapping("/v1/store")
public ApiSuccessResponse retrieveAllStore() {
return ApiSuccessResponse.success(OK_RETRIEVE_ALL_STORE, storeRetrieveService.retrieveAllStore());
}
+ @ApiOperation("[์ธ์ฆ] ์ค์ฒ์ฅ์ ๋ณด๊ธฐ ํ์ด์ง์์ ๋งค์ฅ ์ ๋ณด ํด๋ฆญ ์ - ๋ฆฌํ์คํ
์ด์
์์ธ ์ ๋ณด ์กฐํ")
+ @Auth
@GetMapping("/v1/store/{storeId}")
public ApiSuccessResponse retrieveOneStore(@PathVariable Long storeId) {
return ApiSuccessResponse.success(OK_RETRIEVE_ONE_STORE, storeRetrieveService.retrieveOneStore(storeId));
diff --git a/src/main/java/server/uckgisagi/domain/store/entity/Store.java b/src/main/java/server/uckgisagi/app/store/domain/entity/Store.java
similarity index 85%
rename from src/main/java/server/uckgisagi/domain/store/entity/Store.java
rename to src/main/java/server/uckgisagi/app/store/domain/entity/Store.java
index 2a42235b..d6abc68c 100644
--- a/src/main/java/server/uckgisagi/domain/store/entity/Store.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/entity/Store.java
@@ -1,13 +1,12 @@
-package server.uckgisagi.domain.store.entity;
+package server.uckgisagi.app.store.domain.entity;
import lombok.AccessLevel;
-import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
-import server.uckgisagi.domain.review.entity.Review;
-import server.uckgisagi.domain.store.entity.embedded.Coordinate;
-import server.uckgisagi.domain.store.entity.enumerate.TagName;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+import server.uckgisagi.app.review.domain.entity.Review;
+import server.uckgisagi.app.store.domain.entity.embedded.Coordinate;
+import server.uckgisagi.app.store.domain.entity.enumerate.TagName;
import javax.persistence.*;
import java.util.ArrayList;
@@ -73,5 +72,4 @@ public List getStoreTagValue() {
.map(TagName::getValue)
.collect(Collectors.toList());
}
-
}
diff --git a/src/main/java/server/uckgisagi/domain/store/entity/StoreTag.java b/src/main/java/server/uckgisagi/app/store/domain/entity/StoreTag.java
similarity index 93%
rename from src/main/java/server/uckgisagi/domain/store/entity/StoreTag.java
rename to src/main/java/server/uckgisagi/app/store/domain/entity/StoreTag.java
index a319c689..e462f4f7 100644
--- a/src/main/java/server/uckgisagi/domain/store/entity/StoreTag.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/entity/StoreTag.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.store.entity;
+package server.uckgisagi.app.store.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
@@ -32,5 +32,4 @@ private StoreTag(final Store store, final Tag tag) {
public static StoreTag of(Store store, Tag tag) {
return new StoreTag(store, tag);
}
-
}
diff --git a/src/main/java/server/uckgisagi/domain/store/entity/Tag.java b/src/main/java/server/uckgisagi/app/store/domain/entity/Tag.java
similarity index 75%
rename from src/main/java/server/uckgisagi/domain/store/entity/Tag.java
rename to src/main/java/server/uckgisagi/app/store/domain/entity/Tag.java
index b53f3c32..69172a64 100644
--- a/src/main/java/server/uckgisagi/domain/store/entity/Tag.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/entity/Tag.java
@@ -1,9 +1,9 @@
-package server.uckgisagi.domain.store.entity;
+package server.uckgisagi.app.store.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.store.entity.enumerate.TagName;
+import server.uckgisagi.app.store.domain.entity.enumerate.TagName;
import javax.persistence.*;
@@ -19,5 +19,4 @@ public class Tag {
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private TagName tagName;
-
}
diff --git a/src/main/java/server/uckgisagi/domain/store/entity/embedded/Coordinate.java b/src/main/java/server/uckgisagi/app/store/domain/entity/embedded/Coordinate.java
similarity index 87%
rename from src/main/java/server/uckgisagi/domain/store/entity/embedded/Coordinate.java
rename to src/main/java/server/uckgisagi/app/store/domain/entity/embedded/Coordinate.java
index c681a792..c7af60c2 100644
--- a/src/main/java/server/uckgisagi/domain/store/entity/embedded/Coordinate.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/entity/embedded/Coordinate.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.store.entity.embedded;
+package server.uckgisagi.app.store.domain.entity.embedded;
import lombok.*;
@@ -17,5 +17,4 @@ public class Coordinate {
public static Coordinate of(double xCoordinate, double yCoordinate) {
return new Coordinate(xCoordinate, yCoordinate);
}
-
-}
\ No newline at end of file
+}
diff --git a/src/main/java/server/uckgisagi/domain/store/entity/enumerate/TagName.java b/src/main/java/server/uckgisagi/app/store/domain/entity/enumerate/TagName.java
similarity index 89%
rename from src/main/java/server/uckgisagi/domain/store/entity/enumerate/TagName.java
rename to src/main/java/server/uckgisagi/app/store/domain/entity/enumerate/TagName.java
index 1ed8a043..25310fdf 100644
--- a/src/main/java/server/uckgisagi/domain/store/entity/enumerate/TagName.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/entity/enumerate/TagName.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.store.entity.enumerate;
+package server.uckgisagi.app.store.domain.entity.enumerate;
import lombok.AccessLevel;
import lombok.Getter;
@@ -26,5 +26,4 @@ public String getKey() {
public String getValue() {
return value;
}
-
}
diff --git a/src/main/java/server/uckgisagi/domain/store/repository/StoreRepository.java b/src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepository.java
similarity index 59%
rename from src/main/java/server/uckgisagi/domain/store/repository/StoreRepository.java
rename to src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepository.java
index 48334904..5467abdd 100644
--- a/src/main/java/server/uckgisagi/domain/store/repository/StoreRepository.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepository.java
@@ -1,7 +1,7 @@
-package server.uckgisagi.domain.store.repository;
+package server.uckgisagi.app.store.domain.repository;
import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.store.entity.Store;
+import server.uckgisagi.app.store.domain.entity.Store;
public interface StoreRepository extends JpaRepository, StoreRepositoryCustom {
}
diff --git a/src/main/java/server/uckgisagi/domain/store/repository/StoreRepositoryCustom.java b/src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepositoryCustom.java
similarity index 57%
rename from src/main/java/server/uckgisagi/domain/store/repository/StoreRepositoryCustom.java
rename to src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepositoryCustom.java
index bbc94241..2ead1654 100644
--- a/src/main/java/server/uckgisagi/domain/store/repository/StoreRepositoryCustom.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepositoryCustom.java
@@ -1,6 +1,6 @@
-package server.uckgisagi.domain.store.repository;
+package server.uckgisagi.app.store.domain.repository;
-import server.uckgisagi.domain.store.entity.Store;
+import server.uckgisagi.app.store.domain.entity.Store;
import java.util.List;
diff --git a/src/main/java/server/uckgisagi/domain/store/repository/StoreRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepositoryCustomImpl.java
similarity index 79%
rename from src/main/java/server/uckgisagi/domain/store/repository/StoreRepositoryCustomImpl.java
rename to src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepositoryCustomImpl.java
index 68918f97..e52b5e3c 100644
--- a/src/main/java/server/uckgisagi/domain/store/repository/StoreRepositoryCustomImpl.java
+++ b/src/main/java/server/uckgisagi/app/store/domain/repository/StoreRepositoryCustomImpl.java
@@ -1,12 +1,12 @@
-package server.uckgisagi.domain.store.repository;
+package server.uckgisagi.app.store.domain.repository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
-import server.uckgisagi.domain.store.entity.Store;
+import server.uckgisagi.app.store.domain.entity.Store;
import java.util.List;
-import static server.uckgisagi.domain.store.entity.QStore.*;
+import static server.uckgisagi.app.store.domain.entity.QStore.*;
@RequiredArgsConstructor
public class StoreRepositoryCustomImpl implements StoreRepositoryCustom {
@@ -28,5 +28,4 @@ public Store findStoreByStoreId(Long storeId) {
.where(store.id.eq(storeId))
.fetchOne();
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/store/dto/response/OneStoreResponse.java b/src/main/java/server/uckgisagi/app/store/dto/response/OneStoreResponse.java
index 7e43670b..8ee5bb99 100644
--- a/src/main/java/server/uckgisagi/app/store/dto/response/OneStoreResponse.java
+++ b/src/main/java/server/uckgisagi/app/store/dto/response/OneStoreResponse.java
@@ -1,7 +1,7 @@
package server.uckgisagi.app.store.dto.response;
import lombok.*;
-import server.uckgisagi.domain.store.entity.Store;
+import server.uckgisagi.app.store.domain.entity.Store;
import java.util.ArrayList;
import java.util.List;
@@ -45,5 +45,4 @@ public static OneStoreResponse from(Store store) {
response.tags.addAll(store.getStoreTagValue());
return response;
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/store/dto/response/PreviewStoreDto.java b/src/main/java/server/uckgisagi/app/store/dto/response/PreviewStoreDto.java
index 47b0a1b1..2862ab02 100644
--- a/src/main/java/server/uckgisagi/app/store/dto/response/PreviewStoreDto.java
+++ b/src/main/java/server/uckgisagi/app/store/dto/response/PreviewStoreDto.java
@@ -1,7 +1,7 @@
package server.uckgisagi.app.store.dto.response;
import lombok.*;
-import server.uckgisagi.domain.store.entity.Store;
+import server.uckgisagi.app.store.domain.entity.Store;
@ToString
@Getter
@@ -29,5 +29,4 @@ public static PreviewStoreDto from(Store store) {
.imageUrl(store.getImageUrl())
.build();
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/store/service/StoreRetrieveService.java b/src/main/java/server/uckgisagi/app/store/service/StoreRetrieveService.java
index 274acce3..750483ea 100644
--- a/src/main/java/server/uckgisagi/app/store/service/StoreRetrieveService.java
+++ b/src/main/java/server/uckgisagi/app/store/service/StoreRetrieveService.java
@@ -6,8 +6,8 @@
import server.uckgisagi.app.store.dto.response.AllStoreResponse;
import server.uckgisagi.app.store.dto.response.OneStoreResponse;
import server.uckgisagi.app.store.dto.response.PreviewStoreDto;
-import server.uckgisagi.domain.store.entity.Store;
-import server.uckgisagi.domain.store.repository.StoreRepository;
+import server.uckgisagi.app.store.domain.entity.Store;
+import server.uckgisagi.app.store.domain.repository.StoreRepository;
import java.util.List;
import java.util.stream.Collectors;
@@ -19,13 +19,15 @@ public class StoreRetrieveService {
private final StoreRepository storeRepository;
+ private static final long LIMIT_SIZE = 5L;
+
public AllStoreResponse retrieveAllStore() {
List allStore = storeRepository.findAllStore();
- List popularStoreResponseDto = allStore.stream().limit(5)
+ List popularStoreResponseDto = allStore.stream().limit(LIMIT_SIZE)
.map(PreviewStoreDto::from)
.collect(Collectors.toList());
- List restStoreResponseDto = allStore.stream().skip(5)
+ List restStoreResponseDto = allStore.stream().skip(LIMIT_SIZE)
.map(PreviewStoreDto::from)
.collect(Collectors.toList());
@@ -35,5 +37,4 @@ public AllStoreResponse retrieveAllStore() {
public OneStoreResponse retrieveOneStore(Long storeId) {
return OneStoreResponse.from(StoreServiceUtils.findByStoreId(storeRepository, storeId));
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/store/service/StoreService.java b/src/main/java/server/uckgisagi/app/store/service/StoreService.java
index 582e5ec2..b61ec6cb 100644
--- a/src/main/java/server/uckgisagi/app/store/service/StoreService.java
+++ b/src/main/java/server/uckgisagi/app/store/service/StoreService.java
@@ -2,7 +2,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
-import server.uckgisagi.domain.store.repository.StoreRepository;
+import server.uckgisagi.app.store.domain.repository.StoreRepository;
@Service
@RequiredArgsConstructor
diff --git a/src/main/java/server/uckgisagi/app/store/service/StoreServiceUtils.java b/src/main/java/server/uckgisagi/app/store/service/StoreServiceUtils.java
index a6e9a7d5..1f3d48bb 100644
--- a/src/main/java/server/uckgisagi/app/store/service/StoreServiceUtils.java
+++ b/src/main/java/server/uckgisagi/app/store/service/StoreServiceUtils.java
@@ -4,8 +4,8 @@
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import server.uckgisagi.common.exception.custom.NotFoundException;
-import server.uckgisagi.domain.store.entity.Store;
-import server.uckgisagi.domain.store.repository.StoreRepository;
+import server.uckgisagi.app.store.domain.entity.Store;
+import server.uckgisagi.app.store.domain.repository.StoreRepository;
import static server.uckgisagi.common.exception.ErrorResponseResult.*;
@@ -26,5 +26,4 @@ public static Store findByStoreId(StoreRepository storeRepository, Long storeId)
}
return store;
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/user/UserController.java b/src/main/java/server/uckgisagi/app/user/UserController.java
new file mode 100644
index 00000000..2ba418bc
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/UserController.java
@@ -0,0 +1,41 @@
+package server.uckgisagi.app.user;
+
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import server.uckgisagi.app.user.dto.response.SearchUserResponse;
+import server.uckgisagi.app.user.service.UserService;
+import server.uckgisagi.common.dto.ApiSuccessResponse;
+import server.uckgisagi.common.success.SuccessResponseResult;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.resolver.LoginUserId;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+import static server.uckgisagi.common.success.SuccessResponseResult.OK_SEARCH_USER;
+
+@RestController
+@RequiredArgsConstructor
+public class UserController {
+
+ private final UserService userService;
+
+ @ApiOperation("[์ธ์ฆ] ์ ์ ๊ฒ์ ํ์ด์ง - ์ ์ ๋๋ค์์ผ๋ก ์ฐพ๊ธฐ")
+ @Auth
+ @GetMapping("/v1/user/search")
+ public ApiSuccessResponse> searchUserByNickname(@RequestParam String nickname, @ApiIgnore @LoginUserId Long userId) {
+ return ApiSuccessResponse.success(OK_SEARCH_USER, userService.searchUserByNickname(nickname, userId));
+ }
+
+ @ApiOperation("[์ธ์ฆ] ํ์ ํํด ํ์ด์ง - ํ์ ํํดํ ์ ์ ์ญ์ ")
+ @Auth
+ @DeleteMapping("/v1/user/delete")
+ public ApiSuccessResponse deleteUser(@ApiIgnore @LoginUserId Long userId) {
+ userService.deleteUser(userId);
+ return ApiSuccessResponse.success(OK_SEARCH_USER);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/dictionary/UserDictionary.java b/src/main/java/server/uckgisagi/app/user/domain/dictionary/UserDictionary.java
new file mode 100644
index 00000000..bc6c635c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/dictionary/UserDictionary.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.app.user.domain.dictionary;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class UserDictionary {
+
+ /**
+ * ์ ์ ์ id ๊ฐ์ผ๋ก ํด๋น ์ ์ ๋ฅผ ์ ์ฅํ๋ dictionary
+ */
+ private final Map dictionary;
+
+ public static UserDictionary from(List users) {
+ return new UserDictionary(
+ users.stream()
+ .collect(
+ Collectors.toMap(
+ user -> user.getId(),
+ user -> user
+ )
+ ));
+ }
+
+ public User getUserByUserId(Long userId) {
+ return dictionary.get(userId);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/user/entity/Token.java b/src/main/java/server/uckgisagi/app/user/domain/entity/Token.java
similarity index 87%
rename from src/main/java/server/uckgisagi/domain/user/entity/Token.java
rename to src/main/java/server/uckgisagi/app/user/domain/entity/Token.java
index 8735fc57..9171d8b3 100644
--- a/src/main/java/server/uckgisagi/domain/user/entity/Token.java
+++ b/src/main/java/server/uckgisagi/app/user/domain/entity/Token.java
@@ -1,9 +1,9 @@
-package server.uckgisagi.domain.user.entity;
+package server.uckgisagi.app.user.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
import javax.persistence.*;
@@ -31,5 +31,4 @@ private Token(final Long userId, final String fcmToken) {
public static Token newInstance(Long userId, String fcmToken) {
return new Token(userId, fcmToken);
}
-
}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/entity/User.java b/src/main/java/server/uckgisagi/app/user/domain/entity/User.java
new file mode 100644
index 00000000..a6ff411a
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/entity/User.java
@@ -0,0 +1,149 @@
+package server.uckgisagi.app.user.domain.entity;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import server.uckgisagi.app.block.domain.entity.Block;
+import server.uckgisagi.app.user.domain.entity.embedded.SocialInfo;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.entity.enumerate.UserGrade;
+import server.uckgisagi.app.user.domain.entity.enumerate.UserStatus;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.post.domain.entity.Post;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class User extends AuditingTimeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "user_id", nullable = false)
+ private Long id;
+
+ @Embedded
+ private SocialInfo socialInfo;
+
+ @Column(nullable = false)
+ private String nickname;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, length = 20)
+ private UserGrade grade;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, length = 10)
+ private UserStatus status;
+
+ @OneToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "token_id")
+ private Token token;
+
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
+ private final List posts = new ArrayList<>();
+
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
+ private final List blocks = new ArrayList<>();
+
+ /**
+ * ๋๋ฅผ ํ๋ก์ฐํ๋ ์ ์ List
+ */
+ @OneToMany(mappedBy = "follower", cascade = CascadeType.ALL, orphanRemoval = true)
+ private final List followers = new ArrayList<>(); // ์ฌ๊ธฐ ์๋ followee ๋ ๋ค this(User), follower ๋ ๋๋ฅผ ํ๋ก์ฐํ๋ ์ ๋ค
+
+ /**
+ * ๋ด๊ฐ ํ๋ก์ฐํ๋ ์ ์ List
+ */
+ @OneToMany(mappedBy = "followee", cascade = CascadeType.ALL, orphanRemoval = true)
+ private final List followings = new ArrayList<>(); // ์ฌ๊ธฐ ์๋ followee ๋ ๋ค ๋ด๊ฐ ํ๋ก์ฐํ๋ friend, follower ๋ this(User)
+
+ private User(final String socialId, final SocialType socialType, final String nickname) {
+ this.socialInfo = SocialInfo.of(socialId, socialType);
+ this.nickname = nickname;
+ this.grade = UserGrade.SQUIRE;
+ this.status = UserStatus.ACTIVE;
+ }
+
+ public static User newInstance(String socialId, SocialType socialType, String nickname) {
+ return new User(socialId, socialType, nickname);
+ }
+
+ public void setTokenInfo(Token token) {
+ this.token = token;
+ }
+
+ public String getUserFcmToken() {
+ return this.token.getFcmToken();
+ }
+
+ /**
+ * ๋๋ฅผ ํ๋ก์ฐํ๋ ์ ์ ์ถ๊ฐ
+ * @param follower ๋๋ฅผ ํ๋ก์ฐํ๋ ์ ์
+ */
+ public void addFollower(Follow follower) {
+ this.followers.add(follower);
+ }
+
+ /**
+ * ๋ด๊ฐ ํ๋ก์ฐํ๋ ์ ์ ์ถ๊ฐ
+ * @param myFollowing ๋ด๊ฐ ํ๋ก์ฐํ๋ ์ ์
+ */
+ public void addFollowing(Follow myFollowing) {
+ this.followings.add(myFollowing);
+ }
+
+ public List getMyFollowers() {
+ return this.followers.stream()
+ .map(Follow::getFollower)
+ .collect(Collectors.toList());
+ }
+
+ public List getMyFollowings() {
+ return this.followings.stream()
+ .map(Follow::getFollowee)
+ .collect(Collectors.toList());
+ }
+
+ public void deleteFollower(Follow follower) {
+ this.followers.remove(follower);
+ }
+
+ public void deleteFollowing(Follow following) {
+ this.followings.remove(following);
+ }
+
+ public void addPosts(Post post) {
+ this.posts.add(post);
+ this.changeGrade();
+ }
+
+ private void changeGrade() {
+ switch (this.getPosts().size()) {
+ case 5:
+ this.grade = UserGrade.BARON;
+ break;
+ case 10:
+ this.grade = UserGrade.EARL;
+ break;
+ case 19:
+ this.grade = UserGrade.DUKE;
+ break;
+ case 32:
+ this.grade = UserGrade.LORD;
+ break;
+ case 53:
+ this.grade = UserGrade.KING;
+ break;
+ }
+ }
+
+ void changeNickname(String nickname) {
+ this.nickname = nickname;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/user/entity/embedded/SocialInfo.java b/src/main/java/server/uckgisagi/app/user/domain/entity/embedded/SocialInfo.java
similarity index 87%
rename from src/main/java/server/uckgisagi/domain/user/entity/embedded/SocialInfo.java
rename to src/main/java/server/uckgisagi/app/user/domain/entity/embedded/SocialInfo.java
index b129f964..519869f5 100644
--- a/src/main/java/server/uckgisagi/domain/user/entity/embedded/SocialInfo.java
+++ b/src/main/java/server/uckgisagi/app/user/domain/entity/embedded/SocialInfo.java
@@ -1,10 +1,10 @@
-package server.uckgisagi.domain.user.entity.embedded;
+package server.uckgisagi.app.user.domain.entity.embedded;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.user.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@@ -32,5 +32,4 @@ private SocialInfo(final String socialId, final SocialType socialType) {
public static SocialInfo of(String socialId, SocialType socialType) {
return new SocialInfo(socialId, socialType);
}
-
}
diff --git a/src/main/java/server/uckgisagi/domain/user/entity/enumerate/SocialType.java b/src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/SocialType.java
similarity index 81%
rename from src/main/java/server/uckgisagi/domain/user/entity/enumerate/SocialType.java
rename to src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/SocialType.java
index 7145cc32..818b6e93 100644
--- a/src/main/java/server/uckgisagi/domain/user/entity/enumerate/SocialType.java
+++ b/src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/SocialType.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.user.entity.enumerate;
+package server.uckgisagi.app.user.domain.entity.enumerate;
import lombok.AccessLevel;
import lombok.Getter;
@@ -13,5 +13,4 @@ public enum SocialType {
;
private final String value;
-
}
diff --git a/src/main/java/server/uckgisagi/domain/user/entity/enumerate/UserGrade.java b/src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/UserGrade.java
similarity index 91%
rename from src/main/java/server/uckgisagi/domain/user/entity/enumerate/UserGrade.java
rename to src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/UserGrade.java
index 7c4a2b7e..2d89d8f3 100644
--- a/src/main/java/server/uckgisagi/domain/user/entity/enumerate/UserGrade.java
+++ b/src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/UserGrade.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.user.entity.enumerate;
+package server.uckgisagi.app.user.domain.entity.enumerate;
import lombok.AccessLevel;
import lombok.Getter;
@@ -33,5 +33,4 @@ public String getValue() {
public int getAccumulate() {
return accumulate;
}
-
}
diff --git a/src/main/java/server/uckgisagi/domain/user/entity/enumerate/UserStatus.java b/src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/UserStatus.java
similarity index 50%
rename from src/main/java/server/uckgisagi/domain/user/entity/enumerate/UserStatus.java
rename to src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/UserStatus.java
index d04e3226..98ddfabf 100644
--- a/src/main/java/server/uckgisagi/domain/user/entity/enumerate/UserStatus.java
+++ b/src/main/java/server/uckgisagi/app/user/domain/entity/enumerate/UserStatus.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.user.entity.enumerate;
+package server.uckgisagi.app.user.domain.entity.enumerate;
public enum UserStatus {
ACTIVE,
diff --git a/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepository.java b/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepository.java
new file mode 100644
index 00000000..d0ef9702
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.user.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.user.domain.entity.Token;
+
+public interface TokenRepository extends JpaRepository, TokenRepositoryCustom {
+}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepositoryCustom.java b/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepositoryCustom.java
new file mode 100644
index 00000000..4832f890
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepositoryCustom.java
@@ -0,0 +1,5 @@
+package server.uckgisagi.app.user.domain.repository;
+
+public interface TokenRepositoryCustom {
+ String findFcmTokenByUserId(Long userId);
+}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepositoryCustomImpl.java
new file mode 100644
index 00000000..4f0b155c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/repository/TokenRepositoryCustomImpl.java
@@ -0,0 +1,21 @@
+package server.uckgisagi.app.user.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+
+import static server.uckgisagi.app.user.domain.entity.QToken.*;
+
+@RequiredArgsConstructor
+public class TokenRepositoryCustomImpl implements TokenRepositoryCustom {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public String findFcmTokenByUserId(Long userId) {
+ return query
+ .select(token.fcmToken).distinct()
+ .from(token)
+ .where(token.userId.eq(userId))
+ .fetchFirst();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepository.java b/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepository.java
new file mode 100644
index 00000000..ef8bcb16
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepository.java
@@ -0,0 +1,7 @@
+package server.uckgisagi.app.user.domain.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+
+public interface UserRepository extends JpaRepository, UserRepositoryCustom {
+}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepositoryCustom.java b/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepositoryCustom.java
new file mode 100644
index 00000000..1d99e299
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepositoryCustom.java
@@ -0,0 +1,14 @@
+package server.uckgisagi.app.user.domain.repository;
+
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.util.List;
+
+public interface UserRepositoryCustom {
+ User findUserByUserId(Long userId);
+ List findAllUserByNickname(String nickname, List blockUserIds);
+ User findUserBySocialIdAndSocialType(String socialId, SocialType socialType);
+ boolean existsBySocialIdAndSocialType(String socialId, SocialType socialType);
+ boolean existsByUserId(Long userId);
+}
diff --git a/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepositoryCustomImpl.java b/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepositoryCustomImpl.java
new file mode 100644
index 00000000..f653df62
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/domain/repository/UserRepositoryCustomImpl.java
@@ -0,0 +1,65 @@
+package server.uckgisagi.app.user.domain.repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.entity.User;
+
+import java.util.List;
+
+import static server.uckgisagi.app.user.domain.entity.QUser.*;
+
+@RequiredArgsConstructor
+public class UserRepositoryCustomImpl implements UserRepositoryCustom {
+
+ private final JPAQueryFactory query;
+
+ @Override
+ public User findUserByUserId(Long userId) {
+ return query
+ .selectFrom(user)
+ .where(user.id.eq(userId))
+ .fetchOne();
+ }
+
+ @Override
+ public List findAllUserByNickname(String nickname, List blockUserIds) {
+ return query
+ .selectFrom(user).distinct()
+ .where(
+ user.nickname.contains(nickname),
+ user.id.notIn(blockUserIds)
+ )
+ .fetch();
+ }
+
+ @Override
+ public User findUserBySocialIdAndSocialType(String socialId, SocialType socialType) {
+ return query
+ .selectFrom(user)
+ .where(
+ user.socialInfo.socialId.eq(socialId),
+ user.socialInfo.socialType.eq(socialType)
+ ).fetchOne();
+ }
+
+ @Override
+ public boolean existsBySocialIdAndSocialType(String socialId, SocialType socialType) {
+ return query
+ .selectOne()
+ .from(user)
+ .where(
+ user.socialInfo.socialId.eq(socialId),
+ user.socialInfo.socialType.eq(socialType)
+ ).fetchFirst() != null;
+ }
+
+ @Override
+ public boolean existsByUserId(Long userId) {
+ return query
+ .selectOne()
+ .from(user)
+ .where(user.id.eq(userId))
+ .fetchFirst() != null;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/user/dto/request/CreateUserDto.java b/src/main/java/server/uckgisagi/app/user/dto/request/CreateUserDto.java
new file mode 100644
index 00000000..40162fb7
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/dto/request/CreateUserDto.java
@@ -0,0 +1,21 @@
+package server.uckgisagi.app.user.dto.request;
+
+import lombok.*;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class CreateUserDto {
+
+ private String nickname;
+ private String socialId;
+ private SocialType socialType;
+ private String fcmToken;
+
+ public static CreateUserDto of(String nickname, String socialId, SocialType socialType, String fcmToken) {
+ return new CreateUserDto(nickname, socialId, socialType, fcmToken);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/user/dto/response/FollowStatus.java b/src/main/java/server/uckgisagi/app/user/dto/response/FollowStatus.java
new file mode 100644
index 00000000..128d4ca2
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/dto/response/FollowStatus.java
@@ -0,0 +1,25 @@
+package server.uckgisagi.app.user.dto.response;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.common.model.EnumModel;
+
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum FollowStatus implements EnumModel {
+
+ ACTIVE("ํ๋ก์"),
+ INACTIVE("ํ๋ก์ฐ"),
+ ;
+
+ private final String value;
+
+ @Override
+ public String getKey() {
+ return name();
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/user/dto/response/SearchUserResponse.java b/src/main/java/server/uckgisagi/app/user/dto/response/SearchUserResponse.java
new file mode 100644
index 00000000..6e7f3e88
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/dto/response/SearchUserResponse.java
@@ -0,0 +1,34 @@
+package server.uckgisagi.app.user.dto.response;
+
+import lombok.*;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.UserGrade;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SearchUserResponse {
+
+ private Long userId;
+ private String nickname;
+ private UserGrade grade;
+ private FollowStatus followStatus;
+
+ @Builder(access = AccessLevel.PACKAGE)
+ private SearchUserResponse(Long userId, String nickname, UserGrade grade, FollowStatus followStatus) {
+ this.userId = userId;
+ this.nickname = nickname;
+ this.grade = grade;
+ this.followStatus = followStatus;
+ }
+
+ public static SearchUserResponse of(User user, FollowStatus followStatus) {
+ return SearchUserResponse.builder()
+ .userId(user.getId())
+ .nickname(user.getNickname())
+ .grade(user.getGrade())
+ .followStatus(followStatus)
+ .build();
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/app/user/service/UserService.java b/src/main/java/server/uckgisagi/app/user/service/UserService.java
new file mode 100644
index 00000000..4c6f66d2
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/service/UserService.java
@@ -0,0 +1,58 @@
+package server.uckgisagi.app.user.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.user.dto.request.CreateUserDto;
+import server.uckgisagi.app.user.dto.response.FollowStatus;
+import server.uckgisagi.app.user.dto.response.SearchUserResponse;
+import server.uckgisagi.app.block.domain.entity.Block;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+import server.uckgisagi.app.user.domain.entity.Token;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.repository.TokenRepository;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class UserService {
+
+ private final UserRepository userRepository;
+ private final TokenRepository tokenRepository;
+ private final FollowRepository followRepository;
+
+ @Transactional
+ public User registerUser(CreateUserDto requestDto) {
+ UserServiceUtils.validateNotExistsUser(userRepository, requestDto.getSocialId(), requestDto.getSocialType());
+ User user = userRepository.save(User.newInstance(requestDto.getSocialId(), requestDto.getSocialType(), requestDto.getNickname()));
+ user.setTokenInfo(tokenRepository.save(Token.newInstance(user.getId(), requestDto.getFcmToken())));
+ return user;
+ }
+
+ @Transactional(readOnly = true)
+ public List searchUserByNickname(String nickname, Long userId) {
+ User me = UserServiceUtils.findByUserId(userRepository, userId);
+ List myFollowings = followRepository.findMyFollowingUserByUserId(userId);
+ List blockedUserIds = me.getBlocks().stream()
+ .map(Block::getBlockUserId)
+ .collect(Collectors.toList());
+
+ return userRepository
+ .findAllUserByNickname(nickname, blockedUserIds).stream()
+ .filter(user -> !Objects.equals(user, me)) // JPA Entity ์ equals, hashCode ์ค๋ฒ๋ผ์ด๋ฉ์ด ์ณ์๊ฐ?
+ .map(user -> myFollowings.contains(user)
+ ? SearchUserResponse.of(user, FollowStatus.ACTIVE)
+ : SearchUserResponse.of(user, FollowStatus.INACTIVE)
+ )
+ .collect(Collectors.toList());
+ }
+
+ @Transactional
+ public void deleteUser(Long userId) {
+ userRepository.delete(UserServiceUtils.findByUserId(userRepository, userId));
+ }
+}
diff --git a/src/main/java/server/uckgisagi/app/user/service/UserServiceUtils.java b/src/main/java/server/uckgisagi/app/user/service/UserServiceUtils.java
new file mode 100644
index 00000000..9e0aa699
--- /dev/null
+++ b/src/main/java/server/uckgisagi/app/user/service/UserServiceUtils.java
@@ -0,0 +1,41 @@
+package server.uckgisagi.app.user.service;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import server.uckgisagi.common.exception.custom.ConflictException;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class UserServiceUtils {
+
+ static void validateNotExistsUser(UserRepository userRepository, String socialId, SocialType socialType) {
+ if (userRepository.existsBySocialIdAndSocialType(socialId, socialType)) {
+ throw new ConflictException(String.format("์ด๋ฏธ ์กด์ฌํ๋ ์ ์ (%s - %s) ์
๋๋ค", socialId, socialType), CONFLICT_ALREADY_EXIST_USER_EXCEPTION);
+ }
+ }
+
+ public static void validateExistUser(UserRepository userRepository, Long userId) {
+ if (!userRepository.existsByUserId(userId)) {
+ throw new NotFoundException(String.format("์กด์ฌํ์ง ์๋ ์ ์ (%s) ์
๋๋ค", userId), NOT_FOUND_USER_EXCEPTION);
+ }
+ }
+
+ @NotNull
+ public static User findByUserId(UserRepository userRepository, Long userId) {
+ User user = userRepository.findUserByUserId(userId);
+ if (user == null) {
+ throw new NotFoundException(String.format("์กด์ฌํ์ง ์๋ ์ ์ (%s) ์
๋๋ค", userId), NOT_FOUND_USER_EXCEPTION);
+ }
+ return user;
+ }
+
+ public static User findUserBySocialIdAndSocialType(UserRepository userRepository, String socialId, SocialType socialType) {
+ return userRepository.findUserBySocialIdAndSocialType(socialId, socialType);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/domain/common/AuditingTimeEntity.java b/src/main/java/server/uckgisagi/common/domain/AuditingTimeEntity.java
similarity index 85%
rename from src/main/java/server/uckgisagi/domain/common/AuditingTimeEntity.java
rename to src/main/java/server/uckgisagi/common/domain/AuditingTimeEntity.java
index 18b0e8eb..21971bfd 100644
--- a/src/main/java/server/uckgisagi/domain/common/AuditingTimeEntity.java
+++ b/src/main/java/server/uckgisagi/common/domain/AuditingTimeEntity.java
@@ -1,4 +1,4 @@
-package server.uckgisagi.domain.common;
+package server.uckgisagi.common.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
@@ -23,4 +23,7 @@ public class AuditingTimeEntity {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "Asia/Seoul")
private LocalDateTime updatedAt;
+ public void setTestCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
}
diff --git a/src/main/java/server/uckgisagi/common/dto/ApiSuccessResponse.java b/src/main/java/server/uckgisagi/common/dto/ApiSuccessResponse.java
index 97101101..0f282a9b 100644
--- a/src/main/java/server/uckgisagi/common/dto/ApiSuccessResponse.java
+++ b/src/main/java/server/uckgisagi/common/dto/ApiSuccessResponse.java
@@ -17,7 +17,11 @@ public class ApiSuccessResponse {
private T data;
public static ApiSuccessResponse success(T data) {
- return new ApiSuccessResponse<>(SuccessStatusCode.OK, "", null);
+ return new ApiSuccessResponse<>(SuccessStatusCode.OK, "", data);
+ }
+
+ public static ApiSuccessResponse success(SuccessResponseResult responseResult) {
+ return new ApiSuccessResponse<>(responseResult.getStatusCode(), responseResult.getMessage(), null);
}
public static ApiSuccessResponse success(SuccessResponseResult responseResult, T data) {
diff --git a/src/main/java/server/uckgisagi/common/dto/AuditingTimeResponse.java b/src/main/java/server/uckgisagi/common/dto/AuditingTimeResponse.java
new file mode 100644
index 00000000..88de5125
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/dto/AuditingTimeResponse.java
@@ -0,0 +1,31 @@
+package server.uckgisagi.common.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import server.uckgisagi.common.domain.AuditingTimeEntity;
+
+import java.time.LocalDateTime;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public abstract class AuditingTimeResponse {
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "Asia/Seoul")
+ private LocalDateTime createdAt;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "Asia/Seoul")
+ private LocalDateTime updatedAt;
+
+ protected void setBaseTime(AuditingTimeEntity auditingTimeEntity) {
+ this.createdAt = auditingTimeEntity.getCreatedAt();
+ this.updatedAt = auditingTimeEntity.getUpdatedAt();
+ }
+
+ protected void setCreatedTime(AuditingTimeEntity auditingTimeEntity) {
+ this.createdAt = auditingTimeEntity.getCreatedAt();
+ }
+}
diff --git a/src/main/java/server/uckgisagi/common/exception/ErrorResponseResult.java b/src/main/java/server/uckgisagi/common/exception/ErrorResponseResult.java
index 52d42046..de79495f 100644
--- a/src/main/java/server/uckgisagi/common/exception/ErrorResponseResult.java
+++ b/src/main/java/server/uckgisagi/common/exception/ErrorResponseResult.java
@@ -30,6 +30,9 @@ public enum ErrorResponseResult {
NOT_FOUND_EXCEPTION(NOT_FOUND, "์กด์ฌํ์ง ์์ต๋๋ค"),
NOT_FOUND_STORE_EXCEPTION(NOT_FOUND, "ํด๋น ๋ฆฌํ ์คํ
์ด์
์ด ์กด์ฌํ์ง ์์ต๋๋ค"),
NOT_FOUND_USER_EXCEPTION(NOT_FOUND, "ํํดํ๊ฑฐ๋ ์กด์ฌํ์ง ์๋ ์ ์ ์
๋๋ค"),
+ NOT_FOUND_POST_EXCEPTION(NOT_FOUND, "์กด์ฌํ์ง ์๋ ๊ฒ์๊ธ์
๋๋ค"),
+ NOT_FOUND_SCRAP_EXCEPTION(NOT_FOUND, "์คํฌ๋ฉ๋์ง ์์ ๊ฒ์๊ธ์
๋๋ค"),
+ NOT_FOUND_FOLLOW_RELATION_EXCEPTION(NOT_FOUND, "์กด์ฌํ์ง ์๋ ํ๋ก์ฐ ๊ด๊ณ์
๋๋ค"),
// 405 Method Not Allowed
METHOD_NOT_ALLOWED_EXCEPTION(METHOD_NOT_ALLOWED, "์ง์ํ์ง ์๋ ๋ฉ์๋ ์
๋๋ค"),
@@ -41,6 +44,7 @@ public enum ErrorResponseResult {
CONFLICT_EXCEPTION(CONFLICT, "์ด๋ฏธ ์กด์ฌํฉ๋๋ค"),
CONFLICT_ALREADY_EXIST_USER_EXCEPTION(CONFLICT, "์ด๋ฏธ ์กด์ฌํ๋ ์ ์ ์
๋๋ค"),
CONFLICT_ALREADY_EXIST_STORE_EXCEPTION(CONFLICT, "์ด๋ฏธ ์กด์ฌํ๋ ๋ฆฌํ์คํ
์ด์
์
๋๋ค"),
+ CONFLICT_ALREADY_EXIST_FOLLOW_EXCEPTION(CONFLICT, "์ด๋ฏธ ํ๋ก์ฐ์ค์ธ ์ ์ ์
๋๋ค"),
// 415 Unsupported Media Type
UNSUPPORTED_MEDIA_TYPE_EXCEPTION(UNSUPPORTED_MEDIA_TYPE, "ํด๋นํ๋ ๋ฏธ๋์ด ํ์
์ ์ง์ํ์ง ์์ต๋๋ค."),
diff --git a/src/main/java/server/uckgisagi/common/exception/custom/BusinessException.java b/src/main/java/server/uckgisagi/common/exception/custom/BusinessException.java
new file mode 100644
index 00000000..829a72dd
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/exception/custom/BusinessException.java
@@ -0,0 +1,9 @@
+package server.uckgisagi.common.exception.custom;
+
+import server.uckgisagi.common.exception.ErrorResponseResult;
+
+public class BusinessException extends UckGiSaGiException{
+ public BusinessException(String message, ErrorResponseResult errorResponseResult) {
+ super(message, errorResponseResult);
+ }
+}
diff --git a/src/main/java/server/uckgisagi/common/success/SuccessResponseResult.java b/src/main/java/server/uckgisagi/common/success/SuccessResponseResult.java
index e8e1d36c..a0a4e297 100644
--- a/src/main/java/server/uckgisagi/common/success/SuccessResponseResult.java
+++ b/src/main/java/server/uckgisagi/common/success/SuccessResponseResult.java
@@ -13,12 +13,17 @@ public enum SuccessResponseResult {
// 200 OK
SUCCESS_OK(OK, ""),
OK_RETRIEVE_USER_INFO(OK, "์ ์ ์ ์ ๋ณด๋ฅผ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_USER(OK, "๋๋ค์์ผ๋ก ์ ์ ๋ฅผ ๊ฒ์ํ์ต๋๋ค"),
OK_RETRIEVE_ONE_STORE(OK, "๋ฆฌํ ์คํ
์ด์
์ ์กฐํํ์ต๋๋ค"),
OK_RETRIEVE_ALL_STORE(OK, "๋ชจ๋ ๋ฆฌํ ์คํ
์ด์
์ ์กฐํํ์ต๋๋ค"),
OK_RETRIEVE_STORE_REVIEW(OK, "๋ฆฌํ ์คํ
์ด์
์ ํ๊ธฐ๋ฅผ ์กฐํํ์ต๋๋ค"),
OK_SEARCH_STORE(OK, "๋ฆฌํ ์คํ
์ด์
์ ์์น๋ฅผ ์กฐํํ์ต๋๋ค"),
- OK_SEARCH_MY_POST(OK, "๋์ ์ธ์ฆ ๊ธ์ ์กฐํํ์ต๋๋ค"),
- OK_SEARCH_ALL_POST(OK, "๋ชจ๋ ์ธ์ฆ ๊ธ์ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_POST(OK, "์ฑ๋ฆฐ์ง ๊ธ์ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_MY_SCRAP_POST(OK, "์คํฌ๋ฉํ ์ฑ๋ฆฐ์ง ๊ธ์ ๋ชจ๋ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_MY_SCRAP_POST_DETAIL(OK, "์คํฌ๋ฉํ ์ฑ๋ฆฐ์ง ๊ธ์ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_ALL_POST(OK, "๋ชจ๋ ์ฑ๋ฆฐ์ง ๊ธ์ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_MY_HOME_CONTENTS(OK, "๋์ ํ ๋ทฐ๋ฅผ ์กฐํํ์ต๋๋ค"),
+ OK_SEARCH_FRIEND_HOME_CONTENTS(OK, "์น๊ตฌ์ ํ ๋ทฐ๋ฅผ ์กฐํํ์ต๋๋ค"),
// 201 CREATED
SUCCESS_CREATED(CREATED, ""),
@@ -28,12 +33,21 @@ public enum SuccessResponseResult {
CREATED_STORE(CREATED, "์๋ก์ด ๋ฆฌํ ์คํ
์ด์
์ด ๋ฑ๋ก๋์์ต๋๋ค"),
CREATED_CERTIFICATION_POST(CREATED, "์๋ก์ด ์ธ์ฆ ํฌ์คํธ๊ฐ ๋ฑ๋ก๋์์ต๋๋ค"),
CREATED_UPDATE_STORE(CREATED, "๋ฆฌํ ์คํ
์ด์
์ ๋ณด๊ฐ ์์ ๋์์ต๋๋ค"),
+ CREATED_NOTIFICATION(CREATED, "์๋์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ก๋์์ต๋๋ค"),
+ CREATED_ACCUSE_POST(CREATED, "์ ๊ณ ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ ์๋์์ต๋๋ค."),
// 202 ACCEPTED
SUCCESS_ACCEPTED(ACCEPTED, ""),
// 204 NOT_CONTENT
- SUCCESS_NO_CONTENT(NO_CONTENT, "")
+ SUCCESS_NO_CONTENT(NO_CONTENT, ""),
+ NO_CONTENT_DELETE_POST(NO_CONTENT, "๊ฒ์๋ฌผ ์ญ์ ์ ์ฑ๊ณตํ์ต๋๋ค."),
+ NO_CONTENT_UNFOLLOW_USER(NO_CONTENT, "์ธํ๋ก์ฐ์ ์ฑ๊ณตํ์ต๋๋ค"),
+ NO_CONTENT_SCRAP_POST(NO_CONTENT, "์ฑ๋ฆฐ์ง ๊ธ ์คํฌ๋ฉ์ ์ฑ๊ณตํ์ต๋๋ค"),
+ NO_CONTENT_CANCEL_SCRAP_POST(NO_CONTENT, "์ฑ๋ฆฐ์ง ๊ธ ์คํฌ๋ฉ ์ทจ์์ ์ฑ๊ณตํ์ต๋๋ค"),
+ NO_CONTENT_BLOCK_USER(NO_CONTENT, "์ ์ ์ฐจ๋จ์ ์ฑ๊ณตํ์ต๋๋ค"),
+ NO_CONTENT_CANCEL_BLOCK_USER(NO_CONTENT, "์ ์ ์ฐจ๋จ ํด์ ์ ์ฑ๊ณตํ์ต๋๋ค")
+
;
private final SuccessStatusCode statusCode;
diff --git a/src/main/java/server/uckgisagi/common/type/FileContentType.java b/src/main/java/server/uckgisagi/common/type/FileContentType.java
new file mode 100644
index 00000000..d131fd6f
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/type/FileContentType.java
@@ -0,0 +1,31 @@
+package server.uckgisagi.common.type;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.common.exception.custom.ValidationException;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+@Getter
+@RequiredArgsConstructor
+public enum FileContentType {
+
+ IMAGE("image"),
+ ;
+
+ private final String prefix;
+
+ public void validateAvailableContentType(String contentType) {
+ if (contentType != null && contentType.contains(SEPARATOR) && prefix.equals(getContentTypePrefix(contentType))) {
+ return;
+ }
+ throw new ValidationException(String.format("ํ์ฉ๋์ง ์์ ํ์ผ ํ์ (%s) ์
๋๋ค", contentType), FORBIDDEN_FILE_TYPE_EXCEPTION);
+ }
+
+ private static String getContentTypePrefix(String contentType) {
+ return contentType.split(SEPARATOR)[0];
+ }
+
+ private static final String SEPARATOR = "/";
+
+}
diff --git a/src/main/java/server/uckgisagi/common/type/FileType.java b/src/main/java/server/uckgisagi/common/type/FileType.java
new file mode 100644
index 00000000..0c216517
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/type/FileType.java
@@ -0,0 +1,42 @@
+package server.uckgisagi.common.type;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import server.uckgisagi.common.exception.custom.ValidationException;
+import server.uckgisagi.common.util.FileUtils;
+import server.uckgisagi.common.util.UuidUtils;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+@Getter
+@RequiredArgsConstructor
+public enum FileType {
+
+ STORE_IMAGE("๋ฆฌํ ์คํ
์ด์
๋งค์ฅ ์ด๋ฏธ์ง", "uckgisagi-image/store/", FileContentType.IMAGE),
+ POST_IMAGE("(์ ์ ) ์ธ์ฆ ์ด๋ฏธ์ง", "uckgisagi-image/certification/", FileContentType.IMAGE),
+ ;
+
+ private final String description;
+ private final String directory;
+ private final FileContentType contentType;
+
+ public void validateAvailableContentType(String contentType) {
+ this.contentType.validateAvailableContentType(contentType);
+ }
+
+ /**
+ * ํ์ผ์ ๊ธฐ์กด์ ํ์ฅ์๋ฅผ ์ ์งํ ์ฑ, ์ ๋ํฌํ ํ์ผ์ ์ด๋ฆ์ ๋ฐํํฉ๋๋ค.
+ */
+ public String createUniqueFileNameWithExtension(String originFileName) {
+ if (originFileName == null) {
+ throw new ValidationException("์๋ชป๋ ํ์ผ์ originFilename ์
๋๋ค", FORBIDDEN_FILE_TYPE_EXCEPTION);
+ }
+ String extension = FileUtils.getFileExtension(originFileName);
+ return getFileNameWithDirectory(UuidUtils.generate().concat(extension));
+ }
+
+ private String getFileNameWithDirectory(String fileName) {
+ return this.directory.concat(fileName);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/common/util/FileUtils.java b/src/main/java/server/uckgisagi/common/util/FileUtils.java
new file mode 100644
index 00000000..97e32fd5
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/util/FileUtils.java
@@ -0,0 +1,28 @@
+package server.uckgisagi.common.util;
+
+import server.uckgisagi.common.exception.custom.ValidationException;
+
+import static server.uckgisagi.common.exception.ErrorResponseResult.*;
+
+public class FileUtils {
+
+ /**
+ * ํ์ผ์ ํ์ฅ์๋ฅผ ๋ฐํํฉ๋๋ค.
+ * ์๋ชป๋ ํ์ผ์ ํ์ฅ์์ธ๊ฒฝ์ฐ throws ValidationException
+ *
+ * @param fileName ex) image.png
+ * @return ex) .png
+ */
+ public static String getFileExtension(String fileName) {
+ try {
+ String extension = fileName.substring(fileName.lastIndexOf("."));
+ if (extension.length() < 2) {
+ throw new ValidationException(String.format("์๋ชป๋ ํ์ฅ์ ํ์์ ํ์ผ (%s) ์
๋๋ค", fileName), FORBIDDEN_FILE_TYPE_EXCEPTION);
+ }
+ return extension;
+ } catch (StringIndexOutOfBoundsException e) {
+ throw new ValidationException(String.format("์๋ชป๋ ํ์ฅ์ ํ์์ ํ์ผ (%s) ์
๋๋ค", fileName), FORBIDDEN_FILE_TYPE_EXCEPTION);
+ }
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/common/util/HttpHeaderUtils.java b/src/main/java/server/uckgisagi/common/util/HttpHeaderUtils.java
new file mode 100644
index 00000000..b8717266
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/util/HttpHeaderUtils.java
@@ -0,0 +1,16 @@
+package server.uckgisagi.common.util;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class HttpHeaderUtils {
+
+ public static final String AUTH_HEADER = "Authorization";
+ public static final String BEARER_TOKEN = "Bearer ";
+
+ public static String withBearerToken(String token) {
+ return BEARER_TOKEN.concat(token);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/common/util/JwtUtils.java b/src/main/java/server/uckgisagi/common/util/JwtUtils.java
new file mode 100644
index 00000000..7d70a186
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/util/JwtUtils.java
@@ -0,0 +1,83 @@
+package server.uckgisagi.common.util;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+import server.uckgisagi.app.auth.dto.response.TokenResponse;
+import server.uckgisagi.config.security.JwtConstants;
+
+import java.security.Key;
+import java.util.Date;
+
+@Slf4j
+@Component
+@PropertySource(value = "classpath:application-jwt.yml", factory = YamlPropertySourceFactory.class, ignoreResourceNotFound = true)
+public class JwtUtils {
+
+ private static final long ACCESS_TOKEN_EXPIRES_TIME = 14 * 24 * 2 * 30 * 60 * 1000L; // 30๋ถ
+ private static final long REFRESH_TOKEN_EXPIRES_TIME = 30 * 24 * 60 * 60 * 1000L; // 7์ผ
+
+ private final Key secretKey;
+
+ public JwtUtils(@Value("${jwt.secret}") String secretKey) {
+ byte[] keyBytes = Decoders.BASE64.decode(secretKey);
+ this.secretKey = Keys.hmacShaKeyFor(keyBytes);
+ }
+
+ public TokenResponse createTokenByUserId(Long userId) {
+ long now = (new Date()).getTime();
+ Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRES_TIME);
+ Date refreshTokenExpiresIn = new Date(now + REFRESH_TOKEN_EXPIRES_TIME);
+
+ // accessToken ์์ฑ
+ String accessToken = Jwts.builder()
+ .claim(JwtConstants.USER_ID, userId)
+ .setExpiration(accessTokenExpiresIn)
+ .signWith(secretKey, SignatureAlgorithm.HS512)
+ .compact();
+
+ String refreshToken = Jwts.builder()
+ .setExpiration(refreshTokenExpiresIn)
+ .signWith(secretKey, SignatureAlgorithm.HS512)
+ .compact();
+
+ return TokenResponse.of(accessToken, refreshToken);
+ }
+
+ public boolean validateToken(String token) {
+ try {
+ Jwts.parserBuilder()
+ .setSigningKey(secretKey).build()
+ .parseClaimsJws(token);
+ return true;
+ } catch (SecurityException | MalformedJwtException e) {
+ log.info("Invalid JWT Token", e);
+ } catch (ExpiredJwtException e) {
+ log.info("Expired JWT Token", e);
+ } catch (UnsupportedJwtException e) {
+ log.info("Unsupported JWT Token", e);
+ } catch (IllegalArgumentException e) {
+ log.info("JWT claims string is empty", e);
+ }
+ return false;
+ }
+
+ public Long getUserIdFromJwt(String accessToken) {
+ return parseClaims(accessToken).get(JwtConstants.USER_ID, Long.class);
+ }
+
+ private Claims parseClaims(String accessToken) {
+ try {
+ return Jwts.parserBuilder()
+ .setSigningKey(secretKey).build()
+ .parseClaimsJws(accessToken)
+ .getBody();
+ } catch (ExpiredJwtException e) {
+ return e.getClaims();
+ }
+ }
+}
diff --git a/src/main/java/server/uckgisagi/common/util/RandomNicknameUtils.java b/src/main/java/server/uckgisagi/common/util/RandomNicknameUtils.java
new file mode 100644
index 00000000..5bc69746
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/util/RandomNicknameUtils.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.common.util;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class RandomNicknameUtils {
+
+ private static final List NICK = new ArrayList<>(List.of(
+ "๋ฒ๋ชฉ ํ์ฅ์ ๋๋ฌผ ํ๋ฆฌ๋", "์ฐ๋ ๊ธฐ ์ค๋", "๋ถ๋ฆฌ์๊ฑฐํ๋", "ํผ๋๋ฌผ ํ๋ฆฌ๋",
+ "์๋ฐ์๋", "๋ถ์น ํ๋", "ํ
๋ธ๋ฌ ์ป๋", "์์ฝ๋ฐฑ ์ฐ๋", "ํํํ๋",
+ "์ฌํ์ฉ ํ๋", "์ง์ด ๋ฌผ์ ์ ๊ธฐ๋", "์ค๊ฑฐ์ง ํ๋", "๊ณจ๋จธ๋ฆฌ ์๋"));
+
+ private static final List NAME = new ArrayList<>(List.of(
+ "์กฐ์์กฑ", "๋ชฉ๋๋ฆฌ ๋๋ง๋ฑ", "ํ๋ค", "๋ถ๊ทน๊ณฐ", "ํญ๊ท", "ํฌ๋ฉ๋ผ๋์",
+ "๋ชจ๊ธฐ", "๊ฐ๋ฏธ", "ํธ๋์ด", "๊ณ ์์ด", "๊ธฐ๋ฆฐ", "์ต", "์บฅ๊ฑฐ๋ฃจ",
+ "๋ชฐ๋๋ธ ์ฃผ๋ฏผ", "๋ํ์์"));
+
+ public static String generate() {
+ shuffleRandomNickname();
+ return NICK.get(0) + " " + NAME.get(0);
+ }
+
+ private static void shuffleRandomNickname() {
+ Collections.shuffle(NICK);
+ Collections.shuffle(NAME);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/common/util/UuidUtils.java b/src/main/java/server/uckgisagi/common/util/UuidUtils.java
new file mode 100644
index 00000000..1a155d1c
--- /dev/null
+++ b/src/main/java/server/uckgisagi/common/util/UuidUtils.java
@@ -0,0 +1,16 @@
+package server.uckgisagi.common.util;
+
+import lombok.Getter;
+
+import java.util.UUID;
+
+@Getter
+public class UuidUtils {
+
+ private static final String VERSION = "v1";
+
+ public static String generate() {
+ return String.format("%s-%s", VERSION, UUID.randomUUID());
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/config/aws/S3Config.java b/src/main/java/server/uckgisagi/config/aws/S3Config.java
new file mode 100644
index 00000000..feb4ed00
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/aws/S3Config.java
@@ -0,0 +1,35 @@
+package server.uckgisagi.config.aws;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import server.uckgisagi.common.util.YamlPropertySourceFactory;
+
+@Configuration
+@PropertySource(value = "classpath:application-aws.yml", factory = YamlPropertySourceFactory.class, ignoreResourceNotFound = true)
+public class S3Config {
+
+ @Value("${cloud.aws.credentials.accessKey}")
+ private String accessKey;
+
+ @Value("${cloud.aws.credentials.secretKey}")
+ private String secretKey;
+
+ @Value("${cloud.aws.region.static}")
+ private String region;
+
+ @Bean
+ public AmazonS3Client amazonS3Client() {
+ BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
+ return (AmazonS3Client) AmazonS3ClientBuilder.standard()
+ .withRegion(region)
+ .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
+ .build();
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/config/firebase/FcmInitializer.java b/src/main/java/server/uckgisagi/config/firebase/FirebaseInitializer.java
similarity index 73%
rename from src/main/java/server/uckgisagi/config/firebase/FcmInitializer.java
rename to src/main/java/server/uckgisagi/config/firebase/FirebaseInitializer.java
index ead8058f..5c5d46b4 100644
--- a/src/main/java/server/uckgisagi/config/firebase/FcmInitializer.java
+++ b/src/main/java/server/uckgisagi/config/firebase/FirebaseInitializer.java
@@ -3,26 +3,29 @@
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
+import com.google.firebase.messaging.FirebaseMessaging;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import server.uckgisagi.common.util.YamlPropertySourceFactory;
-import javax.annotation.PostConstruct;
import java.io.IOException;
@Slf4j
@Component
@PropertySource(value = "classpath:application-firebase.yml", factory = YamlPropertySourceFactory.class, ignoreResourceNotFound = true)
-public class FcmInitializer {
+public class FirebaseInitializer {
@Value("${firebase.fcm.config.path}")
private String firebaseConfigPath;
- @PostConstruct
- public void initFirebaseApp() throws IOException {
+ private static FirebaseApp firebaseApp;
+
+ @Bean
+ public void initFirebase() throws IOException {
ClassPathResource resource = new ClassPathResource(firebaseConfigPath);
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(resource.getInputStream());
FirebaseOptions options = FirebaseOptions.builder()
@@ -30,9 +33,12 @@ public void initFirebaseApp() throws IOException {
.build();
if (FirebaseApp.getApps().isEmpty()) {
- FirebaseApp.initializeApp(options);
+ firebaseApp = FirebaseApp.initializeApp(options);
log.info("โ
FirebaseApp Initialization Complete ๐");
}
}
+ public static FirebaseMessaging getFirebaseMessaging() {
+ return FirebaseMessaging.getInstance(firebaseApp);
+ }
}
diff --git a/src/main/java/server/uckgisagi/config/interceptor/Auth.java b/src/main/java/server/uckgisagi/config/interceptor/Auth.java
new file mode 100644
index 00000000..427cf42d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/interceptor/Auth.java
@@ -0,0 +1,11 @@
+package server.uckgisagi.config.interceptor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Auth {
+}
diff --git a/src/main/java/server/uckgisagi/config/interceptor/AuthInterceptor.java b/src/main/java/server/uckgisagi/config/interceptor/AuthInterceptor.java
new file mode 100644
index 00000000..7415bc3e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/interceptor/AuthInterceptor.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.config.interceptor;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+import server.uckgisagi.config.security.JwtConstants;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+@RequiredArgsConstructor
+public class AuthInterceptor implements HandlerInterceptor {
+
+ private final LoginCheckHandler loginCheckHandler;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ if (!(handler instanceof HandlerMethod)) {
+ return true;
+ }
+ HandlerMethod handlerMethod = (HandlerMethod) handler;
+ Auth auth = handlerMethod.getMethodAnnotation(Auth.class);
+ if (auth == null) {
+ return true;
+ }
+ Long userId = loginCheckHandler.getUserId(request);
+ request.setAttribute(JwtConstants.USER_ID, userId);
+ return true;
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/config/interceptor/LoginCheckHandler.java b/src/main/java/server/uckgisagi/config/interceptor/LoginCheckHandler.java
new file mode 100644
index 00000000..2f6bb0b4
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/interceptor/LoginCheckHandler.java
@@ -0,0 +1,32 @@
+package server.uckgisagi.config.interceptor;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import server.uckgisagi.common.exception.custom.UnAuthorizedException;
+import server.uckgisagi.common.util.HttpHeaderUtils;
+import server.uckgisagi.common.util.JwtUtils;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+@RequiredArgsConstructor
+public class LoginCheckHandler {
+
+ private final JwtUtils jwtProvider;
+
+ public Long getUserId(HttpServletRequest request) {
+ String bearerToken = request.getHeader(HttpHeaderUtils.AUTH_HEADER);
+ if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HttpHeaderUtils.BEARER_TOKEN)) {
+ String accessToken = bearerToken.substring(HttpHeaderUtils.BEARER_TOKEN.length());
+ if (jwtProvider.validateToken(accessToken)) {
+ Long userId = jwtProvider.getUserIdFromJwt(accessToken);
+ if (userId != null) {
+ return userId;
+ }
+ }
+ }
+ throw new UnAuthorizedException(String.format("์๋ชป๋ JWT (%s) ์
๋๋ค", bearerToken));
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/config/resolver/LoginUserId.java b/src/main/java/server/uckgisagi/config/resolver/LoginUserId.java
new file mode 100644
index 00000000..2bb04a5d
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/resolver/LoginUserId.java
@@ -0,0 +1,11 @@
+package server.uckgisagi.config.resolver;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LoginUserId {
+}
diff --git a/src/main/java/server/uckgisagi/config/resolver/LoginUserIdResolver.java b/src/main/java/server/uckgisagi/config/resolver/LoginUserIdResolver.java
new file mode 100644
index 00000000..8a94798b
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/resolver/LoginUserIdResolver.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.config.resolver;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+import server.uckgisagi.common.exception.custom.InternalServerException;
+import server.uckgisagi.config.interceptor.Auth;
+import server.uckgisagi.config.security.JwtConstants;
+
+@Component
+public class LoginUserIdResolver implements HandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(LoginUserId.class) && parameter.getParameterType().equals(Long.class);
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
+ if (parameter.getMethodAnnotation(Auth.class) == null) {
+ throw new InternalServerException("@Auth ์ด๋
ธํ
์ด์
์ด ํ์ํ ์ปจํธ๋กค๋ฌ์
๋๋ค");
+ }
+ Object object = webRequest.getAttribute(JwtConstants.USER_ID, 0);
+ if (object == null) {
+ throw new InternalServerException(String.format("USER_ID ๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํ์ต๋. (%s - %s)", parameter.getClass(), parameter.getMethod()));
+ }
+ return object;
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/config/security/JwtConstants.java b/src/main/java/server/uckgisagi/config/security/JwtConstants.java
new file mode 100644
index 00000000..0aa00136
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/security/JwtConstants.java
@@ -0,0 +1,11 @@
+package server.uckgisagi.config.security;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class JwtConstants {
+
+ public static final String USER_ID = "USER_ID";
+
+}
diff --git a/src/main/java/server/uckgisagi/config/security/WebSecurityConfig.java b/src/main/java/server/uckgisagi/config/security/WebSecurityConfig.java
index 5d03d6b7..4f48b68f 100644
--- a/src/main/java/server/uckgisagi/config/security/WebSecurityConfig.java
+++ b/src/main/java/server/uckgisagi/config/security/WebSecurityConfig.java
@@ -3,10 +3,12 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
+@EnableWebSecurity
public class WebSecurityConfig {
/**
@@ -34,5 +36,4 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.httpStrictTransportSecurity().disable();
return http.build();
}
-
}
diff --git a/src/main/java/server/uckgisagi/config/swagger/SwaggerConfig.java b/src/main/java/server/uckgisagi/config/swagger/SwaggerConfig.java
new file mode 100644
index 00000000..dbb57611
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/swagger/SwaggerConfig.java
@@ -0,0 +1,110 @@
+package server.uckgisagi.config.swagger;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.ResponseBuilder;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger.web.DocExpansion;
+import springfox.documentation.swagger.web.UiConfiguration;
+import springfox.documentation.swagger.web.UiConfigurationBuilder;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static springfox.documentation.builders.RequestHandlerSelectors.withClassAnnotation;
+
+@Configuration
+@EnableSwagger2
+@Import(BeanValidatorPluginsConfiguration.class)
+public class SwaggerConfig implements WebMvcConfigurer {
+
+ private static final String API_NAME = "์ต์ง์ฌ์ง";
+ private static final String API_VERSION = "0.0.1";
+ private static final String API_DESCRIPTION = "์ต์ง์ฌ์ง API ๋ช
์ธ์";
+
+ // springfox-swagger-ui ๊ฒฝ๋ก ๋ฑ๋ก
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/swagger-ui/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
+ registry.addResourceHandler("/webjars/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
+ }
+
+ @Bean
+ public UiConfiguration uiConfig() {
+ return UiConfigurationBuilder.builder()
+ .docExpansion(DocExpansion.LIST)
+ .build();
+ }
+
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(apiInfo())
+ .securitySchemes(authorization())
+ .ignoredParameterTypes()
+ .select()
+ .apis(withClassAnnotation(RestController.class))
+ .paths(PathSelectors.ant("/**"))
+ .build()
+ .useDefaultResponseMessages(false)
+ .globalResponses(HttpMethod.GET, this.createGlobalResponseMessages())
+ .globalResponses(HttpMethod.POST, this.createGlobalResponseMessages())
+ .globalResponses(HttpMethod.PUT, this.createGlobalResponseMessages())
+ .globalResponses(HttpMethod.DELETE, this.createGlobalResponseMessages());
+ }
+
+ private List createGlobalResponseMessages() {
+ return Stream.of(
+ HttpStatus.BAD_REQUEST,
+ HttpStatus.UNAUTHORIZED,
+ HttpStatus.CONFLICT,
+ HttpStatus.FORBIDDEN,
+ HttpStatus.NOT_FOUND,
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ HttpStatus.BAD_GATEWAY,
+ HttpStatus.SERVICE_UNAVAILABLE
+ )
+ .map(this::createResponseMessage)
+ .collect(Collectors.toList());
+ }
+
+ private Response createResponseMessage(HttpStatus httpStatus) {
+ return new ResponseBuilder()
+ .code(String.valueOf(httpStatus.value()))
+ .description(httpStatus.getReasonPhrase())
+ .build();
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title(API_NAME)
+ .version(API_VERSION)
+ .description(API_DESCRIPTION)
+ .contact(new Contact(CREATOR, CONTACT_URL, EMAIL))
+ .build();
+ }
+
+ private List authorization() {
+ return List.of(new ApiKey("Authorization", "Authorization", "header"));
+ }
+
+ private static final String CREATOR = "Cho Chan Woo";
+ private static final String EMAIL = "chocw0402@gmail.com";
+ private static final String CONTACT_URL = "https://www.instagram.com/breakfast_wu/";
+
+}
diff --git a/src/main/java/server/uckgisagi/config/web/WebConfig.java b/src/main/java/server/uckgisagi/config/web/WebConfig.java
new file mode 100644
index 00000000..67b602ff
--- /dev/null
+++ b/src/main/java/server/uckgisagi/config/web/WebConfig.java
@@ -0,0 +1,30 @@
+package server.uckgisagi.config.web;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import server.uckgisagi.config.interceptor.AuthInterceptor;
+import server.uckgisagi.config.resolver.LoginUserIdResolver;
+
+import java.util.List;
+
+@Configuration
+@RequiredArgsConstructor
+public class WebConfig implements WebMvcConfigurer {
+
+ private final AuthInterceptor authInterceptor;
+ private final LoginUserIdResolver loginUserIdResolver;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(authInterceptor);
+ }
+
+ @Override
+ public void addArgumentResolvers(List resolvers) {
+ resolvers.add(loginUserIdResolver);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/domain/follow/repository/FollowRepository.java b/src/main/java/server/uckgisagi/domain/follow/repository/FollowRepository.java
deleted file mode 100644
index 9e21de2e..00000000
--- a/src/main/java/server/uckgisagi/domain/follow/repository/FollowRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package server.uckgisagi.domain.follow.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.follow.entity.Follow;
-
-public interface FollowRepository extends JpaRepository {
-}
diff --git a/src/main/java/server/uckgisagi/domain/notification/entity/Notification.java b/src/main/java/server/uckgisagi/domain/notification/entity/Notification.java
deleted file mode 100644
index 9df49f3e..00000000
--- a/src/main/java/server/uckgisagi/domain/notification/entity/Notification.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package server.uckgisagi.domain.notification.entity;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
-import server.uckgisagi.domain.notification.entity.enumerate.NotificationType;
-
-import javax.persistence.*;
-
-@Entity
-@Getter
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Notification extends AuditingTimeEntity {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "notification_id", nullable = false)
- private Long id;
-
- @Column(nullable = false)
- private Long userId; // ์๋์ ๋ณด๋ด๋ ์ ์ ์์ด๋
-
- @Column(nullable = false)
- private Long targetUserId; // ์๋์ ๋ฐ์ ์ ์ ์์ด๋
-
- @Column(nullable = false, length = 20)
- private NotificationType notificationType;
-
-// @Column(columnDefinition = "TEXT", nullable = false)
-// private String notificationMessage;
-
-}
diff --git a/src/main/java/server/uckgisagi/domain/notification/repository/NotificationRepository.java b/src/main/java/server/uckgisagi/domain/notification/repository/NotificationRepository.java
deleted file mode 100644
index 08eefa4d..00000000
--- a/src/main/java/server/uckgisagi/domain/notification/repository/NotificationRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package server.uckgisagi.domain.notification.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.notification.entity.Notification;
-
-public interface NotificationRepository extends JpaRepository {
-}
diff --git a/src/main/java/server/uckgisagi/domain/post/entity/Post.java b/src/main/java/server/uckgisagi/domain/post/entity/Post.java
deleted file mode 100644
index 7fb4bc09..00000000
--- a/src/main/java/server/uckgisagi/domain/post/entity/Post.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package server.uckgisagi.domain.post.entity;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
-import server.uckgisagi.domain.user.entity.User;
-
-import javax.persistence.*;
-
-@Entity
-@Getter
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class Post extends AuditingTimeEntity {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "post_id", nullable = false)
- private Long id;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "user_id")
- private User user;
-
- @Column(nullable = false)
- private String title;
-
- @Column(nullable = false)
- private String content;
-
-}
diff --git a/src/main/java/server/uckgisagi/domain/post/repository/PostRepository.java b/src/main/java/server/uckgisagi/domain/post/repository/PostRepository.java
deleted file mode 100644
index ddfedddb..00000000
--- a/src/main/java/server/uckgisagi/domain/post/repository/PostRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package server.uckgisagi.domain.post.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.post.entity.Post;
-
-public interface PostRepository extends JpaRepository {
-}
diff --git a/src/main/java/server/uckgisagi/domain/postscrap/repository/PostScrapRepository.java b/src/main/java/server/uckgisagi/domain/postscrap/repository/PostScrapRepository.java
deleted file mode 100644
index 38f88ca2..00000000
--- a/src/main/java/server/uckgisagi/domain/postscrap/repository/PostScrapRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package server.uckgisagi.domain.postscrap.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.postscrap.entity.PostScrap;
-
-public interface PostScrapRepository extends JpaRepository {
-}
diff --git a/src/main/java/server/uckgisagi/domain/user/entity/User.java b/src/main/java/server/uckgisagi/domain/user/entity/User.java
deleted file mode 100644
index 227dff6b..00000000
--- a/src/main/java/server/uckgisagi/domain/user/entity/User.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package server.uckgisagi.domain.user.entity;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import server.uckgisagi.domain.common.AuditingTimeEntity;
-import server.uckgisagi.domain.post.entity.Post;
-import server.uckgisagi.domain.user.entity.embedded.SocialInfo;
-import server.uckgisagi.domain.user.entity.enumerate.SocialType;
-import server.uckgisagi.domain.user.entity.enumerate.UserGrade;
-import server.uckgisagi.domain.user.entity.enumerate.UserStatus;
-
-import javax.persistence.*;
-import java.util.ArrayList;
-import java.util.List;
-
-@Entity
-@Getter
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class User extends AuditingTimeEntity {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "user_id", nullable = false)
- private Long id;
-
- @Embedded
- private SocialInfo socialInfo;
-
- @Column(nullable = false)
- private String nickname;
-
- @Enumerated(EnumType.STRING)
- @Column(nullable = false, length = 20)
- private UserGrade grade;
-
- @Enumerated(EnumType.STRING)
- @Column(nullable = false, length = 10)
- private UserStatus status;
-
- @OneToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "token_id")
- private Token token;
-
- @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
- private final List posts = new ArrayList<>();
-
- private User(final String socialId, final SocialType socialType, final String nickname) {
- this.socialInfo = SocialInfo.of(socialId, socialType);
- this.nickname = nickname;
- this.grade = UserGrade.SQUIRE;
- this.status = UserStatus.ACTIVE;
- }
-
- public void setTokenInfo(Token token) {
- this.token = token;
- }
-
- void addPosts(Post post) {
- this.posts.add(post);
- }
-
- void changeNickname(String nickname) {
- this.nickname = nickname;
- }
-
- public void changeGrade(int count) {
- switch (count) {
- case 5:
- this.grade = UserGrade.BARON;
- break;
- case 10:
- this.grade = UserGrade.EARL;
- break;
- case 19:
- this.grade = UserGrade.DUKE;
- break;
- case 32:
- this.grade = UserGrade.LORD;
- break;
- case 53:
- this.grade = UserGrade.KING;
- break;
- }
- }
-
-}
diff --git a/src/main/java/server/uckgisagi/domain/user/repository/UserRepository.java b/src/main/java/server/uckgisagi/domain/user/repository/UserRepository.java
deleted file mode 100644
index 81dca8ee..00000000
--- a/src/main/java/server/uckgisagi/domain/user/repository/UserRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package server.uckgisagi.domain.user.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import server.uckgisagi.domain.user.entity.User;
-
-public interface UserRepository extends JpaRepository {
-}
diff --git a/src/main/java/server/uckgisagi/external/client/apple/AppleAuthApiClient.java b/src/main/java/server/uckgisagi/external/client/apple/AppleAuthApiClient.java
new file mode 100644
index 00000000..a6ae4926
--- /dev/null
+++ b/src/main/java/server/uckgisagi/external/client/apple/AppleAuthApiClient.java
@@ -0,0 +1,13 @@
+package server.uckgisagi.external.client.apple;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import server.uckgisagi.external.client.apple.dto.ApplePublicKeyResponse;
+
+@FeignClient(name = "appleAuthApiClient", url = "https://appleid.apple.com/auth")
+public interface AppleAuthApiClient {
+
+ @GetMapping("/keys")
+ ApplePublicKeyResponse getAppleAuthPublicKey();
+
+}
\ No newline at end of file
diff --git a/src/main/java/server/uckgisagi/external/client/apple/AppleTokenDecoder.java b/src/main/java/server/uckgisagi/external/client/apple/AppleTokenDecoder.java
new file mode 100644
index 00000000..d0ac5c5e
--- /dev/null
+++ b/src/main/java/server/uckgisagi/external/client/apple/AppleTokenDecoder.java
@@ -0,0 +1,8 @@
+package server.uckgisagi.external.client.apple;
+
+
+import org.jetbrains.annotations.NotNull;
+
+public interface AppleTokenDecoder {
+ String getSocialIdFromIdToken(@NotNull String idToken);
+}
diff --git a/src/main/java/server/uckgisagi/external/client/apple/AppleTokenDecoderImpl.java b/src/main/java/server/uckgisagi/external/client/apple/AppleTokenDecoderImpl.java
new file mode 100644
index 00000000..a060ffe0
--- /dev/null
+++ b/src/main/java/server/uckgisagi/external/client/apple/AppleTokenDecoderImpl.java
@@ -0,0 +1,69 @@
+package server.uckgisagi.external.client.apple;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.InvalidClaimException;
+import io.jsonwebtoken.Jwts;
+import lombok.RequiredArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.stereotype.Component;
+import server.uckgisagi.common.exception.ErrorResponseResult;
+import server.uckgisagi.common.exception.custom.ValidationException;
+import server.uckgisagi.external.client.apple.dto.ApplePublicKeyResponse;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Base64;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class AppleTokenDecoderImpl implements AppleTokenDecoder {
+
+ private final AppleAuthApiClient appleApiCaller;
+ private final ObjectMapper objectMapper; // ์ง๋ ฌํ, ์ญ์ง๋ ฌํ
+
+ @Override
+ public String getSocialIdFromIdToken(@NotNull String idToken) {
+ String headerIdToken = idToken.split("\\.")[0];
+ try {
+ Map header = objectMapper.readValue(new String(Base64.getDecoder().decode(headerIdToken), StandardCharsets.UTF_8), new TypeReference<>() {}); // JSON -> JAVA ๊ฐ์ฒด (์ญ์ง๋ ฌํ) : readValue()
+ PublicKey publicKey = getPublicKey(header);
+ Claims claims = Jwts.parserBuilder()
+ .setSigningKey(publicKey)
+ .build()
+ .parseClaimsJws(idToken)
+ .getBody();
+ return claims.getSubject();
+ } catch (ExpiredJwtException e) {
+ throw new ValidationException(String.format("๋ง๋ฃ๋ ์ ํ idToken (%s) ์
๋๋ค (reason: %s)", idToken, e.getMessage(), ErrorResponseResult.VALIDATION_AUTH_TOKEN_EXCEPTION));
+ } catch (JsonProcessingException | InvalidKeySpecException | InvalidClaimException | NoSuchAlgorithmException | IllegalArgumentException e) {
+ throw new ValidationException(String.format("์๋ชป๋ ์ ํ idToken (%s) ์
๋๋ค (reason: %s)", idToken, e.getMessage(), ErrorResponseResult.VALIDATION_AUTH_TOKEN_EXCEPTION));
+ }
+ }
+
+ private PublicKey getPublicKey(Map header) throws InvalidKeySpecException, NoSuchAlgorithmException {
+ ApplePublicKeyResponse response = appleApiCaller.getAppleAuthPublicKey();
+ ApplePublicKeyResponse.JWKSetKey key = response.getMatchedPublicKey(header.get("kid"), header.get("alg"));
+
+ byte[] nBytes = Base64.getUrlDecoder().decode(key.getN());
+ byte[] eBytes = Base64.getUrlDecoder().decode(key.getE());
+
+ BigInteger n = new BigInteger(1, nBytes);
+ BigInteger e = new BigInteger(1, eBytes);
+
+ RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
+ KeyFactory keyFactory = KeyFactory.getInstance(key.getKty());
+
+ return keyFactory.generatePublic(publicKeySpec);
+ }
+
+}
diff --git a/src/main/java/server/uckgisagi/external/client/apple/dto/ApplePublicKeyResponse.java b/src/main/java/server/uckgisagi/external/client/apple/dto/ApplePublicKeyResponse.java
new file mode 100644
index 00000000..895fee5a
--- /dev/null
+++ b/src/main/java/server/uckgisagi/external/client/apple/dto/ApplePublicKeyResponse.java
@@ -0,0 +1,36 @@
+package server.uckgisagi.external.client.apple.dto;
+
+import lombok.*;
+import server.uckgisagi.common.exception.custom.ValidationException;
+
+import java.util.List;
+
+@ToString
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class ApplePublicKeyResponse {
+
+ private List keys;
+
+ public JWKSetKey getMatchedPublicKey(String kid, String alg) {
+ return keys.stream()
+ .filter(key -> key.getKid().equals(kid) && key.getAlg().equals(alg))
+ .findFirst()
+ .orElseThrow(() -> new ValidationException("์ผ์นํ๋ Public Key ๊ฐ ์์ต๋๋ค"));
+ }
+
+ @ToString
+ @Getter
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
+ @AllArgsConstructor(access = AccessLevel.PRIVATE)
+ public static class JWKSetKey {
+ private String alg; // ํ ํฐ์ ์ํธํํ๋ ๋ฐ ์ฌ์ฉ๋๋ ์ํธํ ์๊ณ ๋ฆฌ์ฆ
+ private String e; // RSA ๊ณต๊ฐ ํค์ ์ง์ ๊ฐ
+ private String kid; // ๊ฐ๋ฐ์ ๊ณ์ ์์ ์ป์ 10์๋ฆฌ ์๋ณ์ ํค
+ private String kty; // ํค ์ ํ ๋งค๊ฐ๋ณ์ ์ค์ : "RSA"๋ก ์ค์ ํด์ผํจ
+ private String n; // RSA ๊ณต๊ฐ ํค์ ๋ชจ๋๋ฌ์ค ๊ฐ
+ private String use; // ๊ณต๊ฐ ํค์ ์๋๋ ์ฉ๋
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/application-aws.yml b/src/main/resources/application-aws.yml
new file mode 100644
index 00000000..6989f93b
--- /dev/null
+++ b/src/main/resources/application-aws.yml
@@ -0,0 +1,11 @@
+cloud:
+ aws:
+ credentials:
+ accessKey: ${AWS_ACCESS_KEY_ID}
+ secretKey: ${AWS_SECRET_ACCESS_KEY}
+ s3:
+ bucket: uckgisagi-bucket
+ region:
+ static: ap-northeast-2
+ stack:
+ auto: false
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 627b8dd4..87b30c23 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -3,6 +3,10 @@ spring:
activate:
on-profile: dev
+ mvc:
+ pathmatch:
+ matching-strategy: ant_path_matcher
+
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/uckgisagi_dev?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
diff --git a/src/main/resources/application-jwt.yml b/src/main/resources/application-jwt.yml
new file mode 100644
index 00000000..56013228
--- /dev/null
+++ b/src/main/resources/application-jwt.yml
@@ -0,0 +1,2 @@
+jwt:
+ secret: ${JWT_SECRET}
\ No newline at end of file
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
new file mode 100644
index 00000000..92a69c29
--- /dev/null
+++ b/src/main/resources/application-prod.yml
@@ -0,0 +1,30 @@
+spring:
+ config:
+ activate:
+ on-profile: prod
+
+ mvc:
+ pathmatch:
+ matching-strategy: ant_path_matcher
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: ${DB_URL}
+ username: ${DB_USERNAME}
+ password: ${DB_PASSWORD}
+
+ jpa:
+ database: mysql
+ hibernate:
+ ddl-auto: none
+ properties:
+ hibernate:
+ show_sql: true
+ format_sql: true
+ default_batch_fetch_size: 1000
+
+logging:
+ level:
+ org.hibernate.SQL: debug
+ org.hibernate.type: trace
+ com.amazonaws.util.EC2MetadataUtils: error
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index c8fc4e22..e8146094 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -8,4 +8,7 @@ server:
spring:
profiles:
- default: dev
\ No newline at end of file
+ default: prod
+
+
+
diff --git a/src/main/resources/firebase/uckgisagi-firebase-admin.json b/src/main/resources/firebase/uckgisagi-firebase-admin.json
new file mode 100644
index 00000000..cef84aed
--- /dev/null
+++ b/src/main/resources/firebase/uckgisagi-firebase-admin.json
@@ -0,0 +1,12 @@
+{
+ "type": "service_account",
+ "project_id": "uckgisagi-server-4eae6",
+ "private_key_id": "961ca875112643d4b5031f67a3852f66f753e552",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKtvWE0xaTIPMd\nQBV1DlIaWBfCZdUkQo7UryY8OTX0EUhFg6HNf+6koplYIrJ0dZPwoP6NdYOrdbm+\nun605Z4X+1l5aYEfFpMKWGLWFOxqk49hPnsZMFD0PsbnyTZ7+m/h/gmpfEpQ3nkj\nibzBQjuZLQzT5LaFzDgloy9Ku6RPNT1SsGsBZ3xULHOxMmkP+eWILzCU65f/kG4W\n4lyCoc/kBEP08rS2eIV1OLp3jZJ1mji0tEnVsT4jPOSN6oDXu4yTmEYEy6PvYy0N\noMqfBQAeMHuyqdqjfMY8HXBXaO+Q2Dyzt0SRsMyDSNFfOfyCUG5EOSopwqgq8imU\n8Y1FBMvlAgMBAAECggEAF6RyCx1Bb0RzBlDQj9ftPHRUxQ/yZWm71dNdrr1vbPlN\nCAp7pweKMjpijxRw4sNJz1E/jwkLI8a1tKh0ma2EHEDs5QuoixMrcBPx5w0Gq8Ft\nAgby/XOUpX/i2+qsR5ZkUSO7RcCgvEDOORZZ5OJQKCPIcLgmj4FLdRxMqjcrSS9z\ng8QfnDVxsOKj2WwFkwVUmg4KSC5DIatpzA7z3twvaYfzMsWhtIp783MyHACr9GAQ\n+pN16GDxAibKvP61YrXwRP+q8CpG/c2c9MLrBgk4W4IJ4kvEMeXGjjiu9+y2LIO7\nfyviZqf0+L5NZvOvpFAb+OH/rtqTAyrei/U0M02x6wKBgQD7XEsKqrlISCcb/MRu\nzD0PoP5nINt5CVpkJEgA1rHvGEGxP2tU3HdFr3q6qO1YQM9QVEcfqZPLOCbGEyr9\nnXs05TmHG+DC2jJW0jD2F2f+P9sUrjp2+4GjUQn/67cXgh420qFSbKVR1EDWcGKI\nDLOaxe/zyKzISCrw44Ql7XB0vwKBgQDOdM8Q6q6by7REIFjG5wRN8a+Ld9RTjBr2\nFmoywAOQBYF5A03aD1ojdOt65vcs5DEy4ECrpJwBDrIUo8uBxfiivW1Vdtn+Zxwl\ng13AXmjLyYWAU5KoPkvfJXIEAHZf7dcTzHELBz3Dks33X88UIo88QhWMSt8NQpZm\nIacBaEu0WwKBgHx8S+nffV2P5laVC4+39LGt0PCwNCGwgSTBVyubKIo6ICaxOu3P\nNf68FnMlQE6J4mJtKsBCkqB9ka5dRdhOyvr6X1BLfTfjKjUXagoms2kWpOCMHQZa\nLuz8MJCfY5Dv7xjFngGdLw7kqKvLAvFQIQ8Q4nKAuxmBrEqa0xKZki0vAoGACwNg\nKF7cgaMUMq4nDjU0nZPO8Xmq8en/ZjE76QklJ4GjrnjmpkM7Y7jQ9vVrKhHiLfyY\ndo+JYuUNytwR9xJAeS3xryVv64pEjhu73I8st/JAFOBgamkoUvcEZgJATk25s2ys\nexIf0Vb7db6+pSxSx7weuiUkUOjEbR5OclzF7RECgYEAyP3pdB2tH6dfzYuwrnkf\nNMFTQnedP6AWwl5C1+sRqcEOR/Ggk0zj3Y98eATJczdnPOhRabE5fFyva85bcLPB\nnaOd8h8VXn7ZABUmhZMC1y13tbrFLnSs/iF8x95JdO/KbAwOG8KnQuIYkGqKKKnp\n2zw/oiQMfkPLI4r3Tvy32lA=\n-----END PRIVATE KEY-----\n",
+ "client_email": "firebase-adminsdk-lriyj@uckgisagi-server-4eae6.iam.gserviceaccount.com",
+ "client_id": "103498691720091619822",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-lriyj%40uckgisagi-server-4eae6.iam.gserviceaccount.com"
+}
diff --git a/src/test/java/server/uckgisagi/app/ApplicationContextBeanTest.java b/src/test/java/server/uckgisagi/app/ApplicationContextBeanTest.java
new file mode 100644
index 00000000..2cf3e093
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/ApplicationContextBeanTest.java
@@ -0,0 +1,54 @@
+package server.uckgisagi.app;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.HandlerMapping;
+
+import java.util.Map;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class ApplicationContextBeanTest {
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @DisplayName("๋ฑ๋ก๋ ๋ชจ๋ Bean ์กฐํ")
+ @Test
+ void retrieve_all_beans() {
+ if (applicationContext != null) {
+ String[] beans = applicationContext.getBeanDefinitionNames();
+
+ for (String bean : beans) {
+ System.out.println(bean);
+ }
+ }
+ }
+
+ /**
+ * order: 0 requestMappingHandlerMapping = RequestMappingHandlerMapping
+ *
+ * order: 1 viewControllerHandlerMapping = SimpleUrlHandlerMapping (WebConfig ์์ ํ ์ ์ ๋ฑ๋ก)
+ *
+ * order: 2 beanNameHandlerMapping = BeanNameUrlHandlerMapping
+ *
+ * order: 3 routerFunctionMapping = RouterFunctionMapping
+ *
+ * order: 2147483646 resourceHandlerMapping = SimpleUrlHandlerMapping
+ */
+ @DisplayName("Spring ์์ ์๋์ผ๋ก ์ถ๊ฐํ๋ HnadlerMapping์ ์ฐ์ ์์ ์กฐํ")
+ @Test
+ void retrieve_HandlerMapping() {
+ Map matchingBeans = BeanFactoryUtils
+ .beansOfTypeIncludingAncestors(applicationContext, HandlerMapping.class, true, false);
+ matchingBeans.forEach((k, v) -> {
+ System.out.printf(
+ "order: %s %s = %s %n%n",
+ ((Ordered) v).getOrder(), k, v.getClass().getSimpleName());
+ });
+ }
+}
diff --git a/src/test/java/server/uckgisagi/app/accusation/service/AccusationServiceTest.java b/src/test/java/server/uckgisagi/app/accusation/service/AccusationServiceTest.java
new file mode 100644
index 00000000..6de55a67
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/accusation/service/AccusationServiceTest.java
@@ -0,0 +1,67 @@
+/*
+package server.uckgisagi.app.accusation.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.accusation.dto.AccusationPostReqDto;
+import server.uckgisagi.app.accusation.dto.AccusationPostResDto;
+import server.uckgisagi.domain.accusation.repository.AccusationRepository;
+import server.uckgisagi.domain.post.entity.Post;
+import server.uckgisagi.domain.post.repository.PostRepository;
+import server.uckgisagi.domain.user.entity.User;
+import server.uckgisagi.domain.user.entity.enumerate.SocialType;
+import server.uckgisagi.domain.user.repository.UserRepository;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest
+@Transactional
+class AccusationServiceTest {
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private PostRepository postRepository;
+ @Autowired
+ private AccusationRepository accusationRepository;
+ @Autowired
+ private AccusationService accusationService;
+
+ @DisplayName("์ ๊ณ ์ฑ๊ณต")
+ @Test
+ void accusationTest(){
+ List users = userRepository.saveAll(List.of(
+ User.newInstance("FirstSocialId", SocialType.APPLE, "์ฒซ๋ฒ์งธ"),
+ User.newInstance("SecondSocialId", SocialType.APPLE, "๋๋ฒ์งธ")
+ ));
+
+ List posts = postRepository.saveAll(List.of(
+ Post.newInstance(users.get(0),"", "User1์ ์ฒซ ๋ฒ์งธ ๊ฒ์๋ฌผ", "์ ๊ณง๋ค"),
+ Post.newInstance(users.get(0),"", "User1์ ๋ ๋ฒ์งธ ๊ฒ์๋ฌผ", "์ ๊ณง๋ค"),
+ Post.newInstance(users.get(1),"", "User2์ ์ฒซ ๋ฒ์งธ ๊ฒ์๋ฌผ", "์ ๊ณง๋ค"),
+ Post.newInstance(users.get(1),"", "User2์ ๋ ๋ฒ์งธ ๊ฒ์๋ฌผ", "์ ๊ณง๋ค")
+ ));
+
+ // ๋ ๋ฒ์งธ ์ ์ ๊ฐ postId 1๋ฒ ๊ฒ์๋ฌผ์ ์ ๊ณ
+ AccusationPostResDto accusationPostResponseDto = accusationService.accusePost(new AccusationPostReqDto(0L),1L);
+ assertEquals(accusationPostResponseDto.getPostId(), accusationRepository.findByUserIdAndPostId(0L, 1L));
+
+ }
+
+ @DisplayName("์ด๋ฏธ ์ ๊ณ ๋ด์ญ์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ ์คํจ")
+ @Test
+ void accusationConflictTest(){
+
+ }
+
+ @DisplayName("์ ๊ณ 10๋ฒ ์ด์์ด๋ฉด ํผ๋์ ์ ๋ณด์ด๊ฒ ํจ.")
+ @Test
+ void accusationHideTest(){
+
+ }
+
+}*/
diff --git a/src/test/java/server/uckgisagi/app/auth/AuthServiceProviderTest.java b/src/test/java/server/uckgisagi/app/auth/AuthServiceProviderTest.java
new file mode 100644
index 00000000..c6ce2b66
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/auth/AuthServiceProviderTest.java
@@ -0,0 +1,28 @@
+package server.uckgisagi.app.auth;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import server.uckgisagi.app.auth.provider.AuthServiceProvider;
+import server.uckgisagi.app.auth.service.AuthService;
+import server.uckgisagi.app.auth.service.impl.AppleAuthService;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class AuthServiceProviderTest {
+
+ @Autowired
+ private AuthServiceProvider authServiceProvider;
+
+ @DisplayName("AuthServiceProvider ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ฑ๋ก๋๋ค")
+ @Test
+ void AuthServiceProvider_register_success() {
+ final SocialType APPLE = SocialType.APPLE;
+ AuthService authService = authServiceProvider.getAuthService(APPLE);
+
+ assertTrue(authService instanceof AppleAuthService);
+ }
+}
diff --git a/src/test/java/server/uckgisagi/app/auth/service/TokenServiceTest.java b/src/test/java/server/uckgisagi/app/auth/service/TokenServiceTest.java
new file mode 100644
index 00000000..c57de042
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/auth/service/TokenServiceTest.java
@@ -0,0 +1,31 @@
+package server.uckgisagi.app.auth.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import server.uckgisagi.app.auth.dto.response.TokenResponse;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class TokenServiceTest {
+
+ @Autowired
+ private CreateTokenService createTokenService;
+
+ @Test
+ @DisplayName("์ ์ ์์ด๋๋ก ํ ํฐ ์์ฑํ๊ธฐ")
+ void create_token_by_userId() {
+ final Long USER_ID = 0L;
+
+ TokenResponse tokenInfo = createTokenService.createTokenInfo(USER_ID);
+ System.out.println(tokenInfo.getAccessToken());
+ System.out.println(tokenInfo.getRefreshToken());
+
+ assertAll(
+ () -> assertThat(tokenInfo).isNotNull()
+ );
+ }
+}
diff --git a/src/test/java/server/uckgisagi/app/follow/repository/FollowRepositoryTest.java b/src/test/java/server/uckgisagi/app/follow/repository/FollowRepositoryTest.java
new file mode 100644
index 00000000..99b231ce
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/follow/repository/FollowRepositoryTest.java
@@ -0,0 +1,90 @@
+package server.uckgisagi.app.follow.repository;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = "spring.config.location=classpath:application-test.yml")
+public class FollowRepositoryTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private FollowRepository followRepository;
+
+ private User master;
+ private User firstTarget;
+ private User secondTarget;
+
+ @BeforeEach
+ void setup() {
+ master = User.newInstance("firstSocialId", SocialType.APPLE, "FirstNickname");
+ firstTarget = User.newInstance("secondSocialId", SocialType.APPLE, "SecondNickname");
+ secondTarget = User.newInstance("thirdSocialId", SocialType.APPLE, "ThirdNickname");
+
+ userRepository.saveAll(List.of(master, firstTarget, secondTarget));
+
+ Follow follow = Follow.newInstance(firstTarget, master);
+ Follow follow1 = Follow.newInstance(secondTarget, master);
+ followRepository.saveAll(List.of(follow, follow1));
+
+ master.addFollowing(follow);
+ firstTarget.addFollower(follow);
+
+ master.addFollowing(follow1);
+ secondTarget.addFollower(follow1);
+ }
+
+ @AfterEach
+ void clean() {
+ followRepository.deleteAllInBatch();
+ userRepository.deleteAllInBatch();
+ }
+
+ @DisplayName("๋ด๊ฐ_ํ๋ก์ฐํ๋_์ ์ _๋ ํฌ์งํ ๋ฆฌ_ํ
์คํธ")
+ @Test
+ @Transactional
+ void followRepository_test() {
+ // when
+ System.out.println("================= findMyFollowingUserByUserId =================");
+ /**
+ * inner join ์ฟผ๋ฆฌ ํ๋ฐฉ
+ */
+ List joinQuery = followRepository.findMyFollowingUserByUserId(master.getId());
+ System.out.println(joinQuery.get(0).getId());
+
+ System.out.println("================= getMyFollowing =================");
+ /**
+ * ์ฟผ๋ฆฌ ์๋ ์๊ฐ
+ */
+ List noQuery = master.getMyFollowings();
+ System.out.println(noQuery.get(0).getId());
+
+ // then
+ assertAll(
+ () -> assertThat(joinQuery).isNotEmpty(),
+ () -> assertThat(noQuery).isNotEmpty(),
+ () -> {
+ joinQuery.forEach(user -> {
+ boolean contains = noQuery.contains(user);
+ assertTrue(contains);
+ });
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/server/uckgisagi/app/follow/service/FollowServiceTest.java b/src/test/java/server/uckgisagi/app/follow/service/FollowServiceTest.java
new file mode 100644
index 00000000..0058957c
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/follow/service/FollowServiceTest.java
@@ -0,0 +1,145 @@
+package server.uckgisagi.app.follow.service;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.user.domain.dictionary.UserDictionary;
+import server.uckgisagi.common.exception.custom.ConflictException;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = "spring.config.location=classpath:application-test.yml")
+@Transactional
+public class FollowServiceTest {
+
+ @Autowired
+ private FollowService followService;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private FollowRepository followRepository;
+
+ @AfterEach
+ void clean() {
+ followRepository.deleteAllInBatch();
+ userRepository.deleteAllInBatch();
+ }
+
+ @DisplayName("์ ์ _ํ๋ก์ฐํ๊ธฐ_์ฑ๊ณต")
+ @Test
+ void follow_user_success() {
+ final int FIRST = 0;
+ final int SECOND = 1;
+ // given
+ List users = userRepository.saveAll(List.of(
+ User.newInstance("FirstSocialId", SocialType.APPLE, "์ฒซ๋ฒ์งธ"),
+ User.newInstance("SecondSocialId", SocialType.APPLE, "๋๋ฒ์งธ")
+ ));
+ User user = users.get(FIRST);
+ User target = users.get(SECOND);
+
+ // when
+ UserDictionary userDictionary = followService.followUser(target.getId(), user.getId());
+
+ // then
+ assertAll(
+ () -> assertThat(userDictionary).isNotNull()
+// () -> assertThat(followUser.getFollowers()).isNotNull(),
+// () -> assertThat(followUser.getFollowings()).isEmpty(),
+// () -> assertThat(user.getFollowers()).isEmpty(),
+// () -> assertThat(user.getFollowings()).isNotNull(),
+// () -> assertEquals(followUser.getFollowers().get(FIRST), user.getFollowings().get(FIRST)),
+// () -> assertEquals(
+// followUser.getFollowers().get(FIRST)
+// .getFollower().getId(),
+// user.getId()
+// ),
+// () -> assertEquals(
+// user.getFollowings().get(FIRST)
+// .getFollowee().getId(),
+// followUser.getId()
+// )
+ );
+ }
+
+ @DisplayName("์ด๋ฏธ_ํ๋ก์ฐํ_์ ์ ๋ฅผ_ํ๋ก์ฐํ๋ ค๋_๊ฒฝ์ฐ_์คํจ")
+ @Test
+ void already_follow_user_will_throw_exception() {
+ final int FIRST = 0;
+ final int SECOND = 1;
+
+ List users = userRepository.saveAll(List.of(
+ User.newInstance("FirstSocialId", SocialType.APPLE, "์ฒซ๋ฒ์งธ"),
+ User.newInstance("SecondSocialId", SocialType.APPLE, "๋๋ฒ์งธ")
+ ));
+ User user = users.get(FIRST);
+ User target = users.get(SECOND);
+
+ Follow followInfo = followRepository.save(Follow.newInstance(target, user));
+ target.addFollower(followInfo);
+ user.addFollowing(followInfo);
+
+
+ assertThrows(ConflictException.class, () -> followService.followUser(target.getId(), user.getId()));
+ }
+
+ @DisplayName("์ ์ _์ธํ๋ก์ฐํ๊ธฐ_์ฑ๊ณต")
+ @Test
+ void unfollow_user_success() {
+ final int FIRST = 0;
+ final int SECOND = 1;
+ // given
+ List users = userRepository.saveAll(List.of(
+ User.newInstance("FirstSocialId", SocialType.APPLE, "์ฒซ๋ฒ์งธ"),
+ User.newInstance("SecondSocialId", SocialType.APPLE, "๋๋ฒ์งธ")
+ ));
+ User user = users.get(FIRST);
+ User friend = users.get(SECOND);
+
+ Follow followInfo = followRepository.save(Follow.newInstance(friend, user));
+// friend.addFollower(followInfo);
+// user.addFollowing(followInfo);
+
+ // when
+ System.out.println("=============== UnFollow ================");
+ followService.unfollowUser(friend.getId(), user.getId());
+
+ // then
+ System.out.println("=============== THEN ================");
+ Follow follow = followRepository.findFollowByFolloweeUserIdAndFollowerUserId(friend.getId(), user.getId());
+ assertAll(
+ () -> assertThat(follow).isNull(),
+ () -> assertThat(friend.getFollowers()).isEmpty(),
+ () -> assertThat(friend.getFollowers()).isEmpty()
+ );
+ }
+
+ @DisplayName("์ด๋ฏธ_์ธํ๋ก์ฐํ_์ ์ ๋ฅผ_์ธํ๋ก์ฐ_ํ ๋_์คํจ")
+ @Test
+ void already_unfollow_user_will_throw_exception() {
+ final int FIRST = 0;
+ final int SECOND = 1;
+ List users = userRepository.saveAll(List.of(
+ User.newInstance("FirstSocialId", SocialType.APPLE, "์ฒซ๋ฒ์งธ"),
+ User.newInstance("SecondSocialId", SocialType.APPLE, "๋๋ฒ์งธ")
+ ));
+ User user = users.get(FIRST);
+ User friend = users.get(SECOND);
+
+ assertThrows(NotFoundException.class, () -> followService.unfollowUser(friend.getId(), user.getId()));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/server/uckgisagi/app/home/service/HomeRetrieveServiceTest.java b/src/test/java/server/uckgisagi/app/home/service/HomeRetrieveServiceTest.java
new file mode 100644
index 00000000..6aec3c70
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/home/service/HomeRetrieveServiceTest.java
@@ -0,0 +1,219 @@
+package server.uckgisagi.app.home.service;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.home.dto.response.HomePostResponse;
+import server.uckgisagi.app.home.dto.response.HomeUserResponse;
+import server.uckgisagi.app.home.dto.response.TodayPostStatus;
+import server.uckgisagi.app.home.dto.response.UserResponseDto;
+import server.uckgisagi.app.post.dto.response.PostResponse;
+import server.uckgisagi.app.follow.domain.entity.Follow;
+import server.uckgisagi.app.follow.domain.repository.FollowRepository;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+@Transactional
+public class HomeRetrieveServiceTest {
+
+ @Autowired
+ private HomeRetrieveService homeRetrieveService;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private PostRepository postRepository;
+
+ @Autowired
+ private FollowRepository followRepository;
+
+ private User master;
+ private User friend;
+
+ @BeforeEach
+ void setup() {
+ // given
+ master = User.newInstance("SOCIAL_ID", SocialType.APPLE, "MasterUser");
+ friend = User.newInstance("SOCIAL_ID", SocialType.APPLE, "Friend_1");
+
+ userRepository.saveAll(List.of(master, friend));
+ followRepository.save(Follow.newInstance(friend, master));
+ }
+
+ @AfterEach
+ void clean() {
+ postRepository.deleteAllInBatch();
+ followRepository.deleteAllInBatch();
+ userRepository.deleteAllInBatch();
+ }
+
+ @DisplayName("์๋จ ๋ฐ - ๋์ ๋ด ์น๊ตฌ ๋ชจ๋ ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์์ ๊ฒฝ์ฐ ์ ๋ณด ์กฐํ ์ฑ๊ณต")
+ @Test
+ void retrieve_me_and_friend_info_success_no_post_today() {
+ // when
+ HomeUserResponse response = homeRetrieveService.retrieveMeAndFriendInfo(master.getId());
+
+ // then
+ UserResponseDto myInfo = response.getMyInfo();
+ UserResponseDto friendInfo = response.getFriendInfo().get(ONLY_FRIEND);
+ assertAll(
+ () -> assertThat(response).isNotNull(),
+ () -> assertThat(myInfo).isNotNull(),
+ () -> assertThat(response.getFriendInfo()).isNotEmpty(),
+ () -> assertEquals(myInfo.getUserId(), master.getId()),
+ () -> assertEquals(myInfo.getNickname(), master.getNickname()),
+ () -> assertEquals(myInfo.getGrade(), master.getGrade()),
+ () -> assertEquals(myInfo.getPostStatus(), TodayPostStatus.INACTIVE),
+ () -> assertEquals(friendInfo.getUserId(), friend.getId()),
+ () -> assertEquals(friendInfo.getNickname(), friend.getNickname()),
+ () -> assertEquals(friendInfo.getGrade(), friend.getGrade()),
+ () -> assertEquals(friendInfo.getPostStatus(), TodayPostStatus.INACTIVE)
+ );
+ }
+
+ @DisplayName("์๋จ ๋ฐ - ๋๋ง ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์๋ ๊ฒฝ์ฐ ์ ๋ณด ์กฐํ ์ฑ๊ณต")
+ @Test
+ void retrieve_me_and_friend_info_success_only_me_post_today() {
+ // given
+ postRepository.save(Post.newInstance(master, "imageUrl", "Title", "Content"));
+
+ // when
+ HomeUserResponse response = homeRetrieveService.retrieveMeAndFriendInfo(master.getId());
+
+ // then
+ UserResponseDto myInfo = response.getMyInfo();
+ UserResponseDto friendInfo = response.getFriendInfo().get(ONLY_FRIEND);
+ assertAll(
+ () -> assertThat(response).isNotNull(),
+ () -> assertThat(myInfo).isNotNull(),
+ () -> assertThat(response.getFriendInfo()).isNotEmpty(),
+ () -> assertEquals(myInfo.getUserId(), master.getId()),
+ () -> assertEquals(myInfo.getNickname(), master.getNickname()),
+ () -> assertEquals(myInfo.getGrade(), master.getGrade()),
+ () -> assertEquals(myInfo.getPostStatus(), TodayPostStatus.ACTIVE),
+ () -> assertEquals(friendInfo.getUserId(), friend.getId()),
+ () -> assertEquals(friendInfo.getNickname(), friend.getNickname()),
+ () -> assertEquals(friendInfo.getGrade(), friend.getGrade()),
+ () -> assertEquals(friendInfo.getPostStatus(), TodayPostStatus.INACTIVE)
+ );
+ }
+
+ @DisplayName("์๋จ ๋ฐ - ์น๊ตฌ๋ง ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์๋ ๊ฒฝ์ฐ ์ ๋ณด ์กฐํ ์ฑ๊ณต")
+ @Test
+ void retrieve_me_and_friend_info_success_only_friend_post_today() {
+ // given
+ postRepository.save(Post.newInstance(friend, "imageUrl", "Title", "Content"));
+
+ // when
+ HomeUserResponse response = homeRetrieveService.retrieveMeAndFriendInfo(master.getId());
+
+ // then
+ UserResponseDto myInfo = response.getMyInfo();
+ UserResponseDto friendInfo = response.getFriendInfo().get(ONLY_FRIEND);
+ assertAll(
+ () -> assertThat(response).isNotNull(),
+ () -> assertThat(myInfo).isNotNull(),
+ () -> assertThat(response.getFriendInfo()).isNotEmpty(),
+ () -> assertEquals(myInfo.getUserId(), master.getId()),
+ () -> assertEquals(myInfo.getNickname(), master.getNickname()),
+ () -> assertEquals(myInfo.getGrade(), master.getGrade()),
+ () -> assertEquals(myInfo.getPostStatus(), TodayPostStatus.INACTIVE),
+ () -> assertEquals(friendInfo.getUserId(), friend.getId()),
+ () -> assertEquals(friendInfo.getNickname(), friend.getNickname()),
+ () -> assertEquals(friendInfo.getGrade(), friend.getGrade()),
+ () -> assertEquals(friendInfo.getPostStatus(), TodayPostStatus.ACTIVE)
+ );
+ }
+
+ @DisplayName("์๋จ ๋ฐ - ๋์ ์น๊ตฌ ๋ชจ๋ ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์๋ ๊ฒฝ์ฐ ์ ๋ณด ์กฐํ ์ฑ๊ณต")
+ @Test
+ void retrieve_me_and_friend_info_success_me_and_friend_post_today() {
+ // given
+ postRepository.save(Post.newInstance(master, "imageUrl", "Title", "Content"));
+ postRepository.save(Post.newInstance(friend, "imageUrl", "Title", "Content"));
+
+ // when
+ HomeUserResponse response = homeRetrieveService.retrieveMeAndFriendInfo(master.getId());
+
+ // then
+ UserResponseDto myInfo = response.getMyInfo();
+ UserResponseDto friendInfo = response.getFriendInfo().get(ONLY_FRIEND);
+ assertAll(
+ () -> assertThat(response).isNotNull(),
+ () -> assertThat(myInfo).isNotNull(),
+ () -> assertThat(response.getFriendInfo()).isNotEmpty(),
+ () -> assertEquals(myInfo.getUserId(), master.getId()),
+ () -> assertEquals(myInfo.getNickname(), master.getNickname()),
+ () -> assertEquals(myInfo.getGrade(), master.getGrade()),
+ () -> assertEquals(myInfo.getPostStatus(), TodayPostStatus.ACTIVE),
+ () -> assertEquals(friendInfo.getUserId(), friend.getId()),
+ () -> assertEquals(friendInfo.getNickname(), friend.getNickname()),
+ () -> assertEquals(friendInfo.getGrade(), friend.getGrade()),
+ () -> assertEquals(friendInfo.getPostStatus(), TodayPostStatus.ACTIVE)
+ );
+ }
+
+ @DisplayName("๋์ ํ ์ปจํ
์ธ ์กฐํ ์ฑ๊ณต - ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์๋ ๊ฒฝ์ฐ")
+ @Test
+ void retrieve_my_home_contents_success_not_exist_today_post() {
+ // given
+
+ // when
+
+ // then
+ }
+
+ @DisplayName("๋์ ํ ์ปจํ
์ธ ์กฐํ ์ฑ๊ณต - ์ค๋ ์ฌ๋ฆฐ ์ธ์ฆ๊ธ์ด ์๋ ๊ฒฝ์ฐ")
+ @Test
+ void retrieve_my_home_contents_success_exist_today_post() {
+ // given
+ Post oldPost1 = Post.newInstance(master, "imageUrl", "Title", "Content");
+ Post oldPost2 = Post.newInstance(master, "imageUrl", "Title", "Content");
+ Post oldPost3 = Post.newInstance(master, "imageUrl", "Title", "Content");
+
+ oldPost1.setTestCreatedAt(LocalDateTime.of(LocalDate.of(TODAY_DATE.getYear(), TODAY_DATE.getMonthValue(), 2), LOCAL_TIME));
+ oldPost2.setTestCreatedAt(LocalDateTime.of(LocalDate.of(TODAY_DATE.getYear(), TODAY_DATE.getMonthValue(), 10), LOCAL_TIME));
+ oldPost3.setTestCreatedAt(LocalDateTime.of(LocalDate.of(TODAY_DATE.getYear(), TODAY_DATE.getMonthValue(), 15), LOCAL_TIME));
+
+ Post todayPost = Post.newInstance(master, "imageUrl", "Title", "Content");
+
+ postRepository.saveAll(List.of(oldPost1, oldPost2, oldPost3, todayPost));
+
+ // when
+ HomePostResponse response = homeRetrieveService.retrieveHomeContents(master.getId());
+
+ // then
+
+ List postDates = response.getPostDates();
+ List postResponses = response.getPosts();
+// assertAll(
+// () -> assertThat(response).isNotNull(),
+// () -> assertThat(postDates).isNotEmpty(),
+// () -> assertThat(postResponses).isNotEmpty()
+// );
+ }
+
+ private final LocalDate TODAY_DATE = LocalDate.now(ZoneId.of("Asia/Seoul"));
+
+ private static final LocalTime LOCAL_TIME = LocalTime.of(0, 0);
+ private static final int ONLY_FRIEND = 0;
+}
diff --git a/src/test/java/server/uckgisagi/app/notification/NotificationServiceProviderTest.java b/src/test/java/server/uckgisagi/app/notification/NotificationServiceProviderTest.java
new file mode 100644
index 00000000..8869dd35
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/notification/NotificationServiceProviderTest.java
@@ -0,0 +1,33 @@
+package server.uckgisagi.app.notification;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import server.uckgisagi.app.notification.provider.NotificationServiceProvider;
+import server.uckgisagi.app.notification.service.NotificationService;
+import server.uckgisagi.app.notification.service.impl.FollowNotificationService;
+import server.uckgisagi.app.notification.service.impl.PokeNotificationService;
+import server.uckgisagi.app.notification.domain.entity.enumerate.NotificationType;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class NotificationServiceProviderTest {
+
+ @Autowired
+ private NotificationServiceProvider notificationServiceProvider;
+
+ private static final NotificationType POKE_TYPE = NotificationType.POKE;
+ private static final NotificationType FOLLOW_TYPE = NotificationType.FOLLOW;
+
+ @DisplayName("NotificationServiceProvider ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ฑ๋ก๋๋ค")
+ @Test
+ void AuthServiceProvider_register_success() {
+ NotificationService notificationService1 = notificationServiceProvider.getNotificationService(POKE_TYPE);
+ NotificationService notificationService2 = notificationServiceProvider.getNotificationService(FOLLOW_TYPE);
+
+ assertTrue(notificationService1 instanceof PokeNotificationService);
+ assertTrue(notificationService2 instanceof FollowNotificationService);
+ }
+}
diff --git a/src/test/java/server/uckgisagi/app/review/service/ReviewServiceTest.java b/src/test/java/server/uckgisagi/app/review/service/ReviewServiceTest.java
new file mode 100644
index 00000000..23357b54
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/review/service/ReviewServiceTest.java
@@ -0,0 +1,123 @@
+package server.uckgisagi.app.review.service;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.review.ReviewService;
+import server.uckgisagi.app.review.dto.request.AddReviewRequest;
+import server.uckgisagi.app.review.dto.response.ReviewResponse;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.review.domain.entity.Review;
+import server.uckgisagi.app.review.domain.repository.ReviewRepository;
+import server.uckgisagi.app.store.domain.entity.Store;
+import server.uckgisagi.app.store.domain.repository.StoreRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class ReviewServiceTest {
+
+ @Autowired
+ private ReviewService reviewService;
+
+ @Autowired
+ private ReviewRepository reviewRepository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private StoreRepository storeRepository;
+
+ private User user;
+
+ @BeforeEach
+ void setup() {
+ user = User.newInstance("SOCIAL_ID", SocialType.APPLE, "NICKNAME");
+ userRepository.save(user);
+ }
+
+ @AfterEach
+ void clean() {
+ reviewRepository.deleteAllInBatch();
+ userRepository.deleteAllInBatch();
+ }
+
+ @DisplayName("๋งค์ฅ ๋ฆฌ๋ทฐ ์์ฑ ์ฑ๊ณต")
+ @Test
+ @Transactional
+ void add_review_success() {
+ // given
+ AddReviewRequest request = AddReviewRequest.testBuilder()
+ .storeId(STORE_ID)
+ .content("Content")
+ .build();
+
+ // when
+ reviewService.addReview(request, user.getId());
+
+ // then
+ Store store = storeRepository.findStoreByStoreId(STORE_ID);
+ assertAll(
+ () -> assertThat(store.getReviews()).isNotEmpty()
+// () -> assertEquals(store.getReviews().size(), 1) FIXME Debug ์ ์ ์ ๋์ -> Test ์คํ ์ store ์ review 2๊ฐ add ๋จ (ใ
๋ฒ๊ทธ)
+ );
+
+ // finally
+ store.getReviews().remove(0);
+ }
+
+ @DisplayName("ํด๋น ๋งค์ฅ์ ๋ฆฌ๋ทฐ ์กฐํ ์ฑ๊ณต")
+ @Test
+ @Transactional
+ void retrieve_review_success() {
+ // given
+ Store store = storeRepository.findStoreByStoreId(STORE_ID);
+ Review review = Review.newInstance(store, user, "Comment");
+ store.addReview(review);
+
+ // when
+ List response = reviewService.retrieveReview(STORE_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(response).isNotNull(),
+ () -> assertEquals(response.get(0).getReviewId(), review.getId()),
+ () -> assertEquals(response.get(0).getComment(), review.getComment()),
+ () -> assertEquals(response.get(0).getNickname(), review.getUser().getNickname())
+ );
+
+ // finally
+ store.getReviews().remove(0);
+ }
+
+ @DisplayName("์กด์ฌํ์ง ์๋ ๋งค์ฅ์ ๋ฆฌ๋ทฐ ์์ฑ ์ ์คํจ")
+ @Test
+ void throw_NotFoundException_when_request_not_exist_store() {
+ AddReviewRequest request = AddReviewRequest.testBuilder()
+ .storeId(NOT_EXIST_STORE_ID)
+ .content("Content")
+ .build();
+
+ assertThrows(NotFoundException.class, () -> reviewService.addReview(request, user.getId()));
+ }
+
+ @DisplayName("์กด์ฌํ์ง ์๋ ๋งค์ฅ์ ๋ฆฌ๋ทฐ ์กฐํ ์ ์คํจ")
+ @Test
+ void throw_NotFoundException_when_retrieve_review_not_exist_store() {
+ assertThrows(NotFoundException.class, () -> reviewService.retrieveReview(NOT_EXIST_STORE_ID));
+ }
+
+ private static final Long STORE_ID = 1L;
+ private static final Long NOT_EXIST_STORE_ID = - 1L;
+}
diff --git a/src/test/java/server/uckgisagi/app/scrap/service/ScrapServiceTest.java b/src/test/java/server/uckgisagi/app/scrap/service/ScrapServiceTest.java
new file mode 100644
index 00000000..a49cb0f3
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/scrap/service/ScrapServiceTest.java
@@ -0,0 +1,100 @@
+package server.uckgisagi.app.scrap.service;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.post.domain.entity.Post;
+import server.uckgisagi.app.post.domain.repository.PostRepository;
+import server.uckgisagi.app.scrap.domain.entity.Scrap;
+import server.uckgisagi.app.scrap.domain.repository.ScrapRepository;
+import server.uckgisagi.app.user.domain.entity.User;
+import server.uckgisagi.app.user.domain.entity.enumerate.SocialType;
+import server.uckgisagi.app.user.domain.repository.UserRepository;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class ScrapServiceTest {
+
+ @Autowired
+ private ScrapService scrapService;
+
+ @Autowired
+ private ScrapRepository scrapRepository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private PostRepository postRepository;
+
+ private Post post;
+ private User user;
+
+ @BeforeEach
+ void setup() {
+ user = User.newInstance("SOCIAL_ID", SocialType.APPLE, "NICKNAME");
+ post = Post.newInstance(user, "imageURL", "Title", "Content");
+
+ userRepository.save(user);
+ postRepository.save(post);
+ }
+
+ @AfterEach
+ void clean() {
+ scrapRepository.deleteAllInBatch();
+ postRepository.deleteAllInBatch();
+ userRepository.deleteAllInBatch();
+ }
+
+ @DisplayName("์คํฌ๋ฉ ์ฑ๊ณต")
+ @Test
+ @Transactional
+ void add_scrap_success() {
+ // given
+ final Long USER_ID = user.getId();
+ final Long POST_ID = post.getId();
+
+ // when
+ scrapService.addScrap(POST_ID, USER_ID);
+
+ // then
+ Scrap scrap = scrapRepository.findScrapByPostIdAndUserId(POST_ID, USER_ID);
+ assertThat(scrap).isNotNull();
+ }
+
+ @DisplayName("์คํฌ๋ฉ ์ทจ์ ์ฑ๊ณต")
+ @Test
+ @Transactional
+ void delete_scrap_success() {
+ // given
+ Scrap scrap = Scrap.newInstance(user, post);
+ scrapRepository.save(scrap);
+
+ // when
+ scrapService.deleteScrap(post.getId(), user.getId());
+
+ // then
+ boolean isDeleteScrap = scrapRepository.findById(scrap.getId()).isEmpty();
+ assertTrue(isDeleteScrap);
+ }
+
+ @DisplayName("์กด์ฌํ์ง ์๋ ๊ฒ์๋ฌผ ์คํฌ๋ฉ ์๋ ์ ์คํจ")
+ @Test
+ void throw_NotFoundException_when_request_not_exist_post() {
+ final Long NOT_EXIST_POST_ID = - 1L;
+ assertThrows(NotFoundException.class, () -> scrapService.addScrap(NOT_EXIST_POST_ID, user.getId()));
+ }
+
+ @DisplayName("์คํฌ๋ฉํ์ง ์์ ๊ฒ์๋ฌผ ์คํฌ๋ฉ ์๋ ์ ์คํจ")
+ @Test
+ void throw_NotFoundException_when_request_not_exist_scrap() {
+ assertThrows(NotFoundException.class, () -> scrapService.deleteScrap(post.getId(), user.getId()));
+ }
+}
diff --git a/src/test/java/server/uckgisagi/app/store/service/StoreServiceTest.java b/src/test/java/server/uckgisagi/app/store/service/StoreServiceTest.java
new file mode 100644
index 00000000..623758e7
--- /dev/null
+++ b/src/test/java/server/uckgisagi/app/store/service/StoreServiceTest.java
@@ -0,0 +1,72 @@
+package server.uckgisagi.app.store.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import server.uckgisagi.app.store.dto.response.AllStoreResponse;
+import server.uckgisagi.app.store.dto.response.OneStoreResponse;
+import server.uckgisagi.common.exception.custom.NotFoundException;
+import server.uckgisagi.app.store.domain.entity.Store;
+import server.uckgisagi.app.store.domain.repository.StoreRepository;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {"spring.config.location=classpath:application-test.yml"})
+public class StoreServiceTest {
+
+ @Autowired
+ private StoreRetrieveService storeRetrieveService;
+
+ @Autowired
+ private StoreRepository storeRepository;
+
+ @DisplayName("๋ชจ๋ ๋ฆฌํ์คํ
์ด์
์ ๋ณด ์กฐํ ์ฑ๊ณต (์๋จ ๋งค์ฅ ์ ๋ณด๊ฐ 5๊ฐ)")
+ @Test
+ void all_store_info_retrieve_success() {
+ final int TOP_STORE_COUNT = 5;
+ // when
+ AllStoreResponse response = storeRetrieveService.retrieveAllStore();
+
+ // then
+ assertAll(
+ () -> assertThat(response).isNotNull(),
+ () -> assertEquals(response.getMostPopularStore().size(), TOP_STORE_COUNT)
+ );
+ }
+
+ @DisplayName("๋ฆฌํ์คํ
์ด์
์์ธ ์ ๋ณด ์กฐํ ์ฑ๊ณต")
+ @Test
+ @Transactional(readOnly = true)
+ void one_store_info_retrieve_success() {
+ // given
+ final Long STORE_ID = 1L;
+ Store store = storeRepository.findStoreByStoreId(STORE_ID);
+
+ // when
+ OneStoreResponse response = storeRetrieveService.retrieveOneStore(STORE_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(store).isNotNull(),
+ () -> assertThat(response).isNotNull(),
+ () -> assertEquals(response.getStoreId(), store.getId()),
+ () -> assertEquals(response.getStoreName(), store.getName()),
+ () -> assertEquals(response.getDescription(), store.getDescription()),
+ () -> assertEquals(response.getAddress(), store.getAddress()),
+ () -> assertEquals(response.getImageUrl(), store.getImageUrl()),
+ () -> assertEquals(response.getWebSite(), store.getWebSite()),
+ () -> assertEquals(response.getPhoneNumber(), store.getPhoneNumber()),
+ () -> assertEquals(response.getTags(), store.getStoreTagValue())
+ );
+ }
+
+ @DisplayName("์กด์ฌํ์ง ์๋ ๋งค์ฅ ์ ๋ณด ์กฐํ ์ ์คํจ")
+ @Test
+ void throw_NotFoundException_when_request_not_exist_store() {
+ final Long NOT_EXIST_STORE_ID = - 1L;
+ assertThrows(NotFoundException.class, () -> storeRetrieveService.retrieveOneStore(NOT_EXIST_STORE_ID));
+ }
+}
diff --git a/src/test/java/server/uckgisagi/config/TestConfig.java b/src/test/java/server/uckgisagi/config/TestConfig.java
new file mode 100644
index 00000000..f820ae9c
--- /dev/null
+++ b/src/test/java/server/uckgisagi/config/TestConfig.java
@@ -0,0 +1,21 @@
+package server.uckgisagi.config;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+@TestConfiguration
+public class TestConfig {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Bean
+ public JPAQueryFactory jpaQueryFactory() {
+ return new JPAQueryFactory(entityManager);
+ }
+
+}
diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml
new file mode 100644
index 00000000..ab8c44e0
--- /dev/null
+++ b/src/test/resources/application-test.yml
@@ -0,0 +1,34 @@
+server:
+ port: 8080
+ servlet:
+ encoding:
+ charset: UTF-8
+ force: true
+ context-path: /api
+
+spring:
+
+ mvc:
+ pathmatch:
+ matching-strategy: ant_path_matcher
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/uckgisagi_dev?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
+ username: root
+
+ jpa:
+ database: mysql
+ hibernate:
+ ddl-auto: validate
+ properties:
+ hibernate:
+ show_sql: true
+ format_sql: true
+ default_batch_fetch_size: 1000
+
+logging:
+ level:
+ org.hibernate.SQL: debug
+ org.hibernate.type: trace
+ com.amazonaws.util.EC2MetadataUtils: error
\ No newline at end of file