From 099b5b40992acd98921f31dc4aacbcc20e9c0099 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:30:31 +0900 Subject: [PATCH 01/14] =?UTF-8?q?refactor:=20dto=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/auth/dto/res/{LoginResDto.java => LoginRes.java} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/main/java/com/olive/pribee/module/auth/dto/res/{LoginResDto.java => LoginRes.java} (80%) diff --git a/src/main/java/com/olive/pribee/module/auth/dto/res/LoginResDto.java b/src/main/java/com/olive/pribee/module/auth/dto/res/LoginRes.java similarity index 80% rename from src/main/java/com/olive/pribee/module/auth/dto/res/LoginResDto.java rename to src/main/java/com/olive/pribee/module/auth/dto/res/LoginRes.java index 0d5c852..8c4a4ae 100644 --- a/src/main/java/com/olive/pribee/module/auth/dto/res/LoginResDto.java +++ b/src/main/java/com/olive/pribee/module/auth/dto/res/LoginRes.java @@ -7,15 +7,15 @@ @Getter @Builder(access = AccessLevel.PRIVATE) -public class LoginResDto { +public class LoginRes { @Schema(description = "accessToken", example = "eyJ0eXAiOiJKV1QiLCJhbGc...") private final String accessToken; @Schema(description = "refreshToken", example = "eyJ0eXAiOiJKV1QiLCJhbGc...") private final String refreshToken; - public static LoginResDto of(String accessToken, String refreshToken) { - return LoginResDto.builder() + public static LoginRes of(String accessToken, String refreshToken) { + return LoginRes.builder() .accessToken(accessToken) .refreshToken(refreshToken) .build(); From 51c7c5bdccdf6c06aea06f8db2a3d99e91d4bb0a Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:31:13 +0900 Subject: [PATCH 02/14] =?UTF-8?q?fix:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EB=B0=8F=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=8B=9C,=20redis=20fb=20token=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/auth/service/MemberService.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/olive/pribee/module/auth/service/MemberService.java b/src/main/java/com/olive/pribee/module/auth/service/MemberService.java index bd624f3..3601890 100644 --- a/src/main/java/com/olive/pribee/module/auth/service/MemberService.java +++ b/src/main/java/com/olive/pribee/module/auth/service/MemberService.java @@ -13,7 +13,7 @@ import com.olive.pribee.module.auth.domain.repository.MemberRepository; import com.olive.pribee.module.auth.dto.res.FacebookAuthRes; import com.olive.pribee.module.auth.dto.res.FacebookUserInfoRes; -import com.olive.pribee.module.auth.dto.res.LoginResDto; +import com.olive.pribee.module.auth.dto.res.LoginRes; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; @@ -32,7 +32,7 @@ public class MemberService { // facebook code 기반 facebook 로그인을 통한 접근 jwt 발급 @Transactional - public LoginResDto getAccessToken(String code) { + public LoginRes getAccessToken(String code) { // code 기반 facebook ID 조회 FacebookAuthRes facebookAuthRes = facebookAuthService.getFacebookIdWithToken(code).block(); if (facebookAuthRes == null) { @@ -68,12 +68,12 @@ public LoginResDto getAccessToken(String code) { redisUtil.setOpsForValue(member.getId() + "_refresh", jwtVo.getRefreshToken(), jwtTokenProvider.getREFRESH_TOKEN_EXPIRATION()); - return LoginResDto.of(jwtVo.getAccessToken(), jwtVo.getRefreshToken()); + return LoginRes.of(jwtVo.getAccessToken(), jwtVo.getRefreshToken()); } // refresh token 으로 새로운 accessToken 발급 @Transactional - public LoginResDto getNewAccessToken(String refreshToken) { + public LoginRes getNewAccessToken(String refreshToken) { if (refreshToken.isBlank()) { throw new AppException(GlobalErrorCode.REFRESH_TOKEN_REQUIRED); } @@ -94,21 +94,25 @@ public LoginResDto getNewAccessToken(String refreshToken) { redisUtil.setOpsForValue(tokenMember.getId() + "_refresh", jwtVo.getRefreshToken(), jwtTokenProvider.getREFRESH_TOKEN_EXPIRATION()); - return LoginResDto.of(jwtVo.getAccessToken(), jwtVo.getRefreshToken()); + return LoginRes.of(jwtVo.getAccessToken(), jwtVo.getRefreshToken()); } // 로그아웃 @Transactional public void deleteRefreshToken(Member member) { - // 사용자 refreshToken 삭제 - redisUtil.delete(member.getId() + "_refresh"); + deleteMemberRedis(member); } // 탈퇴 @Transactional public void deleteMember(Member member) { // 사용자 정보 삭제 + deleteMemberRedis(member); memberRepository.delete(member); } + private void deleteMemberRedis(Member member){ + redisUtil.delete(member.getId() + "_fb_access"); + redisUtil.delete(member.getId() + "_refresh"); + } } From f3cc9cd7ab2be2dd1857e7899ff1438957d4cac2 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:31:33 +0900 Subject: [PATCH 03/14] =?UTF-8?q?fix:=20jpa=20auditing=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/olive/pribee/PribeeApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/olive/pribee/PribeeApplication.java b/src/main/java/com/olive/pribee/PribeeApplication.java index 8981a63..140037e 100644 --- a/src/main/java/com/olive/pribee/PribeeApplication.java +++ b/src/main/java/com/olive/pribee/PribeeApplication.java @@ -2,9 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @EnableMongoRepositories +@EnableJpaAuditing @SpringBootApplication public class PribeeApplication { From 4863c04d163c3e6f1244aecbeceaee9126c21702 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:32:20 +0900 Subject: [PATCH 04/14] =?UTF-8?q?refactor:=20@Repository=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20Spring=20=EA=B4=80=EB=A6=AC=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=A5=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pribee/module/auth/domain/repository/MemberRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/olive/pribee/module/auth/domain/repository/MemberRepository.java b/src/main/java/com/olive/pribee/module/auth/domain/repository/MemberRepository.java index 2ca31dc..13d4adb 100644 --- a/src/main/java/com/olive/pribee/module/auth/domain/repository/MemberRepository.java +++ b/src/main/java/com/olive/pribee/module/auth/domain/repository/MemberRepository.java @@ -3,9 +3,11 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import com.olive.pribee.module.auth.domain.entity.Member; +@Repository public interface MemberRepository extends JpaRepository { Optional findByFacebookId(String facebookId); } From 12c24952672993310f077738a23b70d84a1fae9f Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:32:50 +0900 Subject: [PATCH 05/14] =?UTF-8?q?refactor:=20dto=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pribee/module/auth/controller/MemberController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/olive/pribee/module/auth/controller/MemberController.java b/src/main/java/com/olive/pribee/module/auth/controller/MemberController.java index 817809f..73f1097 100644 --- a/src/main/java/com/olive/pribee/module/auth/controller/MemberController.java +++ b/src/main/java/com/olive/pribee/module/auth/controller/MemberController.java @@ -11,7 +11,7 @@ import com.olive.pribee.global.common.DataResponseDto; import com.olive.pribee.global.common.ResponseDto; import com.olive.pribee.module.auth.domain.entity.Member; -import com.olive.pribee.module.auth.dto.res.LoginResDto; +import com.olive.pribee.module.auth.dto.res.LoginRes; import com.olive.pribee.module.auth.service.MemberService; import lombok.RequiredArgsConstructor; @@ -26,13 +26,13 @@ public class MemberController implements MemberControllerDocs { @GetMapping("/login/facebook") public ResponseEntity getLogin(@RequestHeader("facebook-code") String code){ - LoginResDto resDto = memberService.getAccessToken(code); + LoginRes resDto = memberService.getAccessToken(code); return ResponseEntity.status(201).body(DataResponseDto.of(resDto, 201)); } @GetMapping("/token") public ResponseEntity getAccessToken(@RequestHeader("Authorization-Refresh") String refreshToken) { - LoginResDto resDto = memberService.getNewAccessToken(refreshToken); + LoginRes resDto = memberService.getNewAccessToken(refreshToken); return ResponseEntity.status(201).body(DataResponseDto.of(resDto, 201)); } From 5fe78195d7179187ccc8f0f883f8a3479339d37e Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:34:16 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20Quiz=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/quiz/domain/entity/Quiz.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/domain/entity/Quiz.java diff --git a/src/main/java/com/olive/pribee/module/quiz/domain/entity/Quiz.java b/src/main/java/com/olive/pribee/module/quiz/domain/entity/Quiz.java new file mode 100644 index 0000000..f565dd6 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/domain/entity/Quiz.java @@ -0,0 +1,70 @@ +package com.olive.pribee.module.quiz.domain.entity; + +import com.olive.pribee.global.common.BaseTime; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "quiz") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +public class Quiz extends BaseTime { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + private String question; + + @NotNull + private String answer1; + + @NotNull + private String answer2; + + @NotNull + private Boolean answerIsOne; + + @NotNull + private String reason; + + @NotNull + private int participate; + + @NotNull + private int wrong; + + public static Quiz of(@NotNull String question, @NotNull String answer1, @NotNull String answer2, + @NotNull Boolean answerIsOne, @NotNull String reason) { + return Quiz.builder() + .question(question) + .answer1(answer1) + .answer2(answer2) + .answerIsOne(answerIsOne) + .reason(reason) + .participate(0) + .wrong(0) + .build(); + } + + public void plusParticipate() { + this.participate += 1; + } + + public void plusWrong() { + this.wrong += 1; + } +} From a847635262d81d233ca7795f0c3bf9cd80225f21 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:34:40 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20Quiz=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=ED=95=98=EA=B2=8C=203=EA=B0=9C=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20JPA=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quiz/domain/repository/QuizRepository.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/domain/repository/QuizRepository.java diff --git a/src/main/java/com/olive/pribee/module/quiz/domain/repository/QuizRepository.java b/src/main/java/com/olive/pribee/module/quiz/domain/repository/QuizRepository.java new file mode 100644 index 0000000..34b0c08 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/domain/repository/QuizRepository.java @@ -0,0 +1,18 @@ +package com.olive.pribee.module.quiz.domain.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import com.olive.pribee.module.quiz.domain.entity.Quiz; + +@Repository +public interface QuizRepository extends JpaRepository { + @Query(value = "SELECT * FROM quiz " + + "WHERE id >= (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM quiz))) " + + "ORDER BY id LIMIT 3", nativeQuery = true) + List findTop3RandomOptimized(); + +} From 9c14d395ec228e50841c6db77b92d6493715142f Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:35:05 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20Quiz=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/quiz/error/QuizErrorCode.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/error/QuizErrorCode.java diff --git a/src/main/java/com/olive/pribee/module/quiz/error/QuizErrorCode.java b/src/main/java/com/olive/pribee/module/quiz/error/QuizErrorCode.java new file mode 100644 index 0000000..1e20dac --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/error/QuizErrorCode.java @@ -0,0 +1,20 @@ +package com.olive.pribee.module.quiz.error; + +import org.springframework.http.HttpStatus; + +import com.olive.pribee.global.error.ErrorCode; + +import lombok.Getter; + +@Getter +public enum QuizErrorCode implements ErrorCode { + INVALID_QUIZ_ID(HttpStatus.NOT_FOUND, "해당하는 퀴즈가 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + QuizErrorCode(HttpStatus httpStatus, String message) { + this.httpStatus = httpStatus; + this.message = message; + } +} From 1259bccd326ac1a6bcccfc394e92c0bb3ca0fdc7 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:35:27 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20Quiz=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=ED=95=9C=203=EA=B0=9C=20=EB=8B=B5=EB=B3=80=20dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/quiz/dto/res/QuizRandomRes.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/dto/res/QuizRandomRes.java diff --git a/src/main/java/com/olive/pribee/module/quiz/dto/res/QuizRandomRes.java b/src/main/java/com/olive/pribee/module/quiz/dto/res/QuizRandomRes.java new file mode 100644 index 0000000..2035aa8 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/dto/res/QuizRandomRes.java @@ -0,0 +1,42 @@ +package com.olive.pribee.module.quiz.dto.res; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder(access = AccessLevel.PRIVATE) +@Schema(description = "퀴즈 랜덤 제공 응답 DTO") +public class QuizRandomRes { + @Schema(description = "퀴즈 id", example = "1") + private final Long id; + @Schema(description = "퀴즈 질문", example = "회사 임원으로부터 \"기밀 문서를 확인해 주세요\"라는 이메일이 왔다. 어떻게 해야 할까?") + private final String question; + @Schema(description = "답변 항목 1", example = "이메일 주소를 꼼꼼히 확인하고, 수상하면 별도 연락하여 진위를 확인한다.") + private final String answer1; + @Schema(description = "답변 항목 2", example = "상사 이메일이므로 즉시 문서를 다운로드하여 확인한다.") + private final String answer2; + @Schema(description = "답변 항목 1이 정답인지 여부", example = "true") + private final Boolean answerIsOne; + @Schema(description = "답변 설명", example = "타깃 스피어 피싱 공격은 특정 조직을 노리는 정교한 공격이므로, 반드시 진위를 확인해야 한다.") + private final String reason; + @Schema(description = "오답자 비율", example = "32.23") + private final float wrongPortion; + + public static QuizRandomRes of(Long id, String question, String answer1, String answer2, Boolean answerIsOne, + String reason, int participant, int wrong) { + float wrongPortion = participant == 0 ? 0 : (float)wrong / participant * 100; + + return QuizRandomRes.builder() + .id(id) + .question(question) + .answer1(answer1) + .answer2(answer2) + .answerIsOne(answerIsOne) + .reason(reason) + .wrongPortion(Math.round(wrongPortion * 100) / 100.0f) + .build(); + } + +} From bbcacad0f530f60007c5ed7cd50a369b25d9c517 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:35:54 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20Quiz=20=EC=A0=95=EB=8B=B5?= =?UTF-8?q?=EB=A5=A0=20=EB=B0=98=EC=98=81=20=EC=9A=94=EC=B2=AD=20dto=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quiz/dto/req/QuizAnswerListReq.java | 33 +++++++++++++++++++ .../module/quiz/dto/req/QuizAnswerReq.java | 16 +++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerListReq.java create mode 100644 src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerReq.java diff --git a/src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerListReq.java b/src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerListReq.java new file mode 100644 index 0000000..6b08d18 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerListReq.java @@ -0,0 +1,33 @@ +package com.olive.pribee.module.quiz.dto.req; + +import java.util.List; + +import org.hibernate.validator.constraints.UniqueElements; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; + +@Schema(description = "퀴즈 답변 체크 요청 DTO") +public record QuizAnswerListReq( + @Schema(description = "요청하는 퀴즈 및 정답 여부 리스트", example = "[\n" + + " {\n" + + " \"id\": 1,\n" + + " \"isCorrect\": false\n" + + " },\n" + + " {\n" + + " \"id\": 3,\n" + + " \"isCorrect\": true\n" + + " },\n" + + " {\n" + + " \"id\": 4,\n" + + " \"isCorrect\": false\n" + + " }\n" + + "]") + @NotEmpty + @Size(min = 1, message = "퀴즈 답변 리스트는 최소 1개 이상이어야 합니다.") + @UniqueElements + List quizAnswerReqs + +) { +} \ No newline at end of file diff --git a/src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerReq.java b/src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerReq.java new file mode 100644 index 0000000..efff155 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/dto/req/QuizAnswerReq.java @@ -0,0 +1,16 @@ +package com.olive.pribee.module.quiz.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; + +@Schema(description = "퀴즈 답변 체크 요청 DTO") +public record QuizAnswerReq( + @Schema(description = "요청하는 퀴즈 id", example = "1") + @NotEmpty + Long id, + @Schema(description = "퀴즈 정답 여부", example = "true") + @NotEmpty + Boolean isCorrect + +) { +} \ No newline at end of file From 58db73489b460a71592871eb132b884cbd848449 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:36:39 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20Quiz=20=EB=9E=9C=EB=8D=A4=203?= =?UTF-8?q?=EA=B0=9C=20=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EC=A0=95=EB=8B=B5?= =?UTF-8?q?=EB=A5=A0=20=EB=B0=98=EC=98=81=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/quiz/service/QuizService.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/service/QuizService.java diff --git a/src/main/java/com/olive/pribee/module/quiz/service/QuizService.java b/src/main/java/com/olive/pribee/module/quiz/service/QuizService.java new file mode 100644 index 0000000..47e7a70 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/service/QuizService.java @@ -0,0 +1,69 @@ +package com.olive.pribee.module.quiz.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.olive.pribee.global.error.exception.AppException; +import com.olive.pribee.module.quiz.domain.entity.Quiz; +import com.olive.pribee.module.quiz.domain.repository.QuizRepository; +import com.olive.pribee.module.quiz.dto.req.QuizAnswerListReq; +import com.olive.pribee.module.quiz.dto.req.QuizAnswerReq; +import com.olive.pribee.module.quiz.dto.res.QuizRandomRes; +import com.olive.pribee.module.quiz.error.QuizErrorCode; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class QuizService { + private final QuizRepository quizRepository; + + // 퀴즈 3개 가져오기 + public List getRandomThreeQuiz() { + // quiz 랜덤 추출 + List quizzes = quizRepository.findTop3RandomOptimized(); + + // dto 매핑 + return quizzes.stream().map( + quiz -> QuizRandomRes.of( + quiz.getId(), + quiz.getQuestion(), + quiz.getAnswer1(), + quiz.getAnswer2(), + quiz.getAnswerIsOne(), + quiz.getReason(), + quiz.getParticipate(), + quiz.getWrong() + )).collect(Collectors.toList()); + } + + // 퀴즈 답변 반영 + @Transactional + public void postQuizWrongPortion(QuizAnswerListReq quizAnswerReqs) { + // id 기반 모든 퀴즈 조회 + Map quizMap = quizRepository.findAllById( + quizAnswerReqs.quizAnswerReqs().stream().map(QuizAnswerReq::id).toList() + ).stream().collect(Collectors.toMap(Quiz::getId, quiz -> quiz)); + + if (quizMap.size() != quizAnswerReqs.quizAnswerReqs().size()) { + throw new AppException(QuizErrorCode.INVALID_QUIZ_ID); + } + + // 참여 횟수 및 오답 수 증가 처리 + for (QuizAnswerReq req : quizAnswerReqs.quizAnswerReqs()) { + Quiz quiz = quizMap.get(req.id()); + quiz.plusParticipate(); + if (!req.isCorrect()) { + quiz.plusWrong(); + } + } + + // 변경된 퀴즈 목록 저장 + quizRepository.saveAll(quizMap.values()); + } +} From ad348281cba76be78e633532f850b4944f842278 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:36:48 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20Quiz=20=EB=9E=9C=EB=8D=A4=203?= =?UTF-8?q?=EA=B0=9C=20=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EC=A0=95=EB=8B=B5?= =?UTF-8?q?=EB=A5=A0=20=EB=B0=98=EC=98=81=20=EC=B2=98=EB=A6=AC=20api=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quiz/controller/QuizController.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/controller/QuizController.java diff --git a/src/main/java/com/olive/pribee/module/quiz/controller/QuizController.java b/src/main/java/com/olive/pribee/module/quiz/controller/QuizController.java new file mode 100644 index 0000000..01b86d9 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/controller/QuizController.java @@ -0,0 +1,39 @@ +package com.olive.pribee.module.quiz.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.olive.pribee.global.common.DataResponseDto; +import com.olive.pribee.global.common.ResponseDto; +import com.olive.pribee.module.quiz.dto.req.QuizAnswerListReq; +import com.olive.pribee.module.quiz.dto.res.QuizRandomRes; +import com.olive.pribee.module.quiz.service.QuizService; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/api/quiz/") +@RequiredArgsConstructor +public class QuizController implements QuizControllerDocs { + private final QuizService quizService; + + @GetMapping + public ResponseEntity getRandomThreeQuiz() { + List resDto = quizService.getRandomThreeQuiz(); + return ResponseEntity.status(200).body(DataResponseDto.of(resDto, 200)); + } + + @PostMapping + public ResponseEntity postQuizWrongPortion(@RequestBody @Valid QuizAnswerListReq quizAnswerReqs) { + quizService.postQuizWrongPortion(quizAnswerReqs); + return ResponseEntity.ok(ResponseDto.of(200)); + } + +} From f58fac073116e6a7af7f77a78f71a0af1b0b4a15 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:37:40 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feat:=20Quiz=20api=20path=20jwt=20?= =?UTF-8?q?=EC=97=86=EC=9D=B4=20=EC=A7=84=ED=96=89=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EC=A0=95=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pribee/global/common/filter/JwtAuthenticationFilter.java | 3 ++- .../java/com/olive/pribee/global/config/SecurityConfig.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/olive/pribee/global/common/filter/JwtAuthenticationFilter.java b/src/main/java/com/olive/pribee/global/common/filter/JwtAuthenticationFilter.java index bc9446c..83dc165 100644 --- a/src/main/java/com/olive/pribee/global/common/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/olive/pribee/global/common/filter/JwtAuthenticationFilter.java @@ -40,7 +40,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // accessToken 이 필요없는 경우 필터링 없이 처리 if (requestURI.startsWith("/api/auth/token") || - requestURI.startsWith("/api/auth/login/facebook")) { + requestURI.startsWith("/api/auth/login/facebook") || + requestURI.startsWith("/api/quiz/**")) { chain.doFilter(request, response); return; } diff --git a/src/main/java/com/olive/pribee/global/config/SecurityConfig.java b/src/main/java/com/olive/pribee/global/config/SecurityConfig.java index ad7a441..29a029d 100644 --- a/src/main/java/com/olive/pribee/global/config/SecurityConfig.java +++ b/src/main/java/com/olive/pribee/global/config/SecurityConfig.java @@ -45,6 +45,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers( "/api/auth/token", "/api/auth/login/facebook", + "/api/quiz/**", "/swagger-ui/**", "/webjars/**", "/swagger-ui.html", From bfc612f37589aff642383f20bd0541794c889679 Mon Sep 17 00:00:00 2001 From: yerim1ee Date: Thu, 6 Mar 2025 08:38:02 +0900 Subject: [PATCH 14/14] =?UTF-8?q?docs:=20Quiz=20=EB=9E=9C=EB=8D=A4=203?= =?UTF-8?q?=EA=B0=9C=20=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EC=A0=95=EB=8B=B5?= =?UTF-8?q?=EB=A5=A0=20=EB=B0=98=EC=98=81=20=EC=B2=98=EB=A6=AC=20api=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=B6=94=EA=B0=80=20[PRBE-27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quiz/controller/QuizControllerDocs.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/main/java/com/olive/pribee/module/quiz/controller/QuizControllerDocs.java diff --git a/src/main/java/com/olive/pribee/module/quiz/controller/QuizControllerDocs.java b/src/main/java/com/olive/pribee/module/quiz/controller/QuizControllerDocs.java new file mode 100644 index 0000000..4e185d0 --- /dev/null +++ b/src/main/java/com/olive/pribee/module/quiz/controller/QuizControllerDocs.java @@ -0,0 +1,85 @@ +package com.olive.pribee.module.quiz.controller; + +import org.springframework.http.ResponseEntity; + +import com.olive.pribee.global.common.ResponseDto; +import com.olive.pribee.module.quiz.dto.req.QuizAnswerListReq; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Quiz", description = "퀴즈 관련 API") +public interface QuizControllerDocs { + + @Operation(summary = "랜덤 퀴즈 3개 추출 API", description = "") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "Created", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ResponseDto.class), + examples = + @ExampleObject(value = "{\n" + + " \"code\": 200,\n" + + " \"message\": \"OK\",\n" + + " \"data\": [\n" + + " {\n" + + " \"id\": 3,\n" + + " \"question\": \"생년월일을 SNS 프로필에서 비공개로 설정했지만, 아이디 또는 비밀번호에 생일과 연관된 숫자를 사용했다.\",\n" + + " \"answer1\": \"괜찮다\",\n" + + " \"answer2\": \"위험하다\",\n" + + " \"answerIsOne\": false,\n" + + " \"reason\": \"생년월일은 해킹 공격자가 비밀번호를 추측할 때 가장 먼저 시도하는 정보 중 하나다. SNS 비밀번호는 생일과 무관한 강력한 조합으로 설정하는 것이 안전하다.\",\n" + + " \"wrongPortion\": 0\n" + + " },\n" + + " {\n" + + " \"id\": 9,\n" + + " \"question\": \"친구와 찍은 사진을 SNS에 올릴 때 친구에게 허락을 받지 않았다.\",\n" + + " \"answer1\": \"괜찮다\",\n" + + " \"answer2\": \"위험하다\",\n" + + " \"answerIsOne\": false,\n" + + " \"reason\": \"본인은 괜찮더라도 친구가 사진 공개를 원치 않을 수 있으며, 초상권 및 개인정보 보호 문제가 발생할 수 있다.\",\n" + + " \"wrongPortion\": 0\n" + + " },\n" + + " {\n" + + " \"id\": 18,\n" + + " \"question\": \"회사 이메일로 온 첨부파일을 열기 전에 해야 할 행동은?\",\n" + + " \"answer1\": \"발신자를 확인하고, 출처가 불분명하면 열지 않는다.\",\n" + + " \"answer2\": \"업무 관련 내용이라면 바로 다운로드해서 실행한다.\",\n" + + " \"answerIsOne\": true,\n" + + " \"reason\": \"악성코드가 포함된 이메일 첨부파일은 기업 내 랜섬웨어 감염을 일으킬 수 있다.\",\n" + + " \"wrongPortion\": 0\n" + + " }\n" + + " ]\n" + + "}") + ) + ) + }) + ResponseEntity getRandomThreeQuiz(); + + @Operation(summary = "정답률 처리 API", description = "id 와 정답 여부를 받아 정답률을 처리하는 API 입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ResponseDto.class), + examples = + @ExampleObject(value = "{ \"code\": 200, \"message\": \"OK\" }") + ) + ), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ResponseDto.class), + examples = @ExampleObject(value = "{ \"code\": 400, \"message\": \"해당하는 퀴즈가 없습니다.\" }") + ) + ) + }) + ResponseEntity postQuizWrongPortion(QuizAnswerListReq quizAnswerReqs); + +} +