From 217691dfe35544e7570ef6e695a5badddd3157f0 Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Wed, 27 Dec 2023 16:48:39 +0900 Subject: [PATCH 01/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6f64d8..f124d1e 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ ## ๐Ÿ“š ๊ฐœ๋ฐœ ๊ณผ์ • - [[Briefing] API ๋ฒ„์ „ ๊ด€๋ฆฌ & ์ „๋žต ํŒจํ„ด](https://velog.io/@cekim/briefing-api-versioning) - +- [[Briefing] Spotless๋กœ ์ฝ”๋“œ ํฌ๋งท ์œ ์ง€ํ•˜๊ธฐ](https://velog.io/@cekim/briefing-spotless)
From 3aaf28b872c0aa88c98e78491dc518f1661b7941 Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Wed, 27 Dec 2023 17:36:38 +0900 Subject: [PATCH 02/28] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Refactor:=20Spotl?= =?UTF-8?q?ess=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :recycle: Refactor: Spotless ํ”Œ๋Ÿฌ๊ทธ์ธ ์ ์šฉ ์ฝ”๋“œ ํฌ๋งคํ„ฐ * :recycle: Refactor: googleJavaFormat().aosp() ์ ์šฉ --- build.gradle | 31 +- .../java/briefing/BriefingApplication.java | 7 +- .../java/briefing/GlobalControllerAdvice.java | 28 +- .../briefing/base/BaseDateTimeEntity.java | 17 +- .../java/briefing/base/BaseException.java | 8 +- .../java/briefing/base/BaseExceptionType.java | 4 +- .../briefing/briefing/api/BriefingApi.java | 187 ++++++----- .../briefing/api/BriefingConverter.java | 94 +++--- .../application/BriefingCommandService.java | 36 +- .../application/BriefingQueryService.java | 33 +- .../context/BriefingQueryContext.java | 6 +- .../context/BriefingQueryContextFactory.java | 17 +- .../application/dto/BriefingRequestDTO.java | 24 +- .../application/dto/BriefingRequestParam.java | 13 +- .../application/dto/BriefingResponseDTO.java | 41 +-- .../strategy/BriefingQueryStrategy.java | 6 +- .../strategy/BriefingV1QueryStrategy.java | 24 +- .../strategy/BriefingV2QueryStrategy.java | 27 +- .../briefing/briefing/domain/Article.java | 39 ++- .../briefing/briefing/domain/Briefing.java | 67 ++-- .../briefing/domain/BriefingArticle.java | 31 +- .../briefing/domain/BriefingType.java | 24 +- .../briefing/briefing/domain/TimeOfDay.java | 7 +- .../domain/repository/ArticleRepository.java | 6 +- .../repository/BriefingArticleRepository.java | 5 +- .../repository/BriefingCustomRepository.java | 11 +- .../BriefingCustomRepositoryImpl.java | 106 +++--- .../domain/repository/BriefingRepository.java | 13 +- .../briefing/chatting/api/ChattingApi.java | 75 +++-- .../chatting/api/ChattingConverter.java | 40 ++- .../chatting/application/ChatGptClient.java | 107 +++--- .../application/ChattingCommandService.java | 116 ++++--- .../application/ChattingQueryService.java | 30 +- .../application/dto/ChattingRequest.java | 11 +- .../application/dto/ChattingResponse.java | 18 +- .../briefing/chatting/domain/Chatting.java | 49 +-- .../briefing/chatting/domain/GptModel.java | 29 +- .../briefing/chatting/domain/Message.java | 48 +-- .../briefing/chatting/domain/MessageRole.java | 37 +- .../domain/repository/ChattingRepository.java | 7 +- .../domain/repository/MessageRepository.java | 7 +- .../briefing/common/enums/APIVersion.java | 7 +- .../common/response/CommonResponse.java | 11 +- .../java/briefing/config/GlobalWebConfig.java | 60 ++-- .../briefing/config/JpaAuditingConfig.java | 4 +- .../java/briefing/config/QueryDslConfig.java | 12 +- .../briefing/config/RestTemplateConfig.java | 8 +- .../java/briefing/config/SwaggerConfig.java | 33 +- .../converter/APIVersionRequestConverter.java | 6 +- .../BriefingTypeRequestConverter.java | 11 +- .../converter/GptModelRequestConverter.java | 11 +- .../MessageRoleRequestConverter.java | 11 +- .../converter/SocialTypeRequestConverter.java | 4 +- .../converter/TimeOfDayConverter.java | 3 +- .../java/briefing/exception/ErrorCode.java | 71 ++-- .../briefing/exception/ExceptionAdvice.java | 151 +++++---- .../briefing/exception/GeneralException.java | 2 +- .../exception/common/ApiErrorResult.java | 7 +- .../handler/AppleOAuthException.java | 2 +- .../exception/handler/BriefingException.java | 2 +- .../exception/handler/ChattingException.java | 2 +- .../handler/CustomFeignClientException.java | 2 +- .../handler/JwtAuthenticationException.java | 5 +- .../exception/handler/MemberException.java | 2 +- .../handler/RefreshTokenException.java | 2 +- .../FeignClientExceptionErrorDecoder.java | 29 +- .../oauth/apple/client/AppleOauth2Client.java | 10 +- .../config/AppleOauth2FeignConfiguration.java | 7 +- .../feign/oauth/apple/dto/ApplePublicKey.java | 28 +- .../oauth/apple/dto/ApplePublicKeyList.java | 7 +- .../google/client/GoogleOauth2Client.java | 8 +- .../GoogleOauth2FeignConfiguration.java | 5 +- .../oauth/google/dto/GoogleUserInfo.java | 2 +- .../java/briefing/member/api/MemberApi.java | 218 ++++++++---- .../briefing/member/api/MemberConverter.java | 27 +- .../application/MemberCommandService.java | 66 ++-- .../application/MemberQueryService.java | 30 +- .../member/application/dto/MemberRequest.java | 6 +- .../application/dto/MemberResponse.java | 26 +- .../java/briefing/member/domain/Member.java | 12 +- .../briefing/member/domain/SocialType.java | 3 +- .../domain/repository/MemberRepository.java | 7 +- .../member/exception/MemberException.java | 2 +- .../briefing/redis/domain/RefreshToken.java | 8 +- .../repository/RefreshTokenRepository.java | 7 +- .../briefing/redis/service/RedisService.java | 1 - .../redis/service/RedisServiceImpl.java | 60 ++-- src/main/java/briefing/root/api/RootApi.java | 13 +- .../java/briefing/scrap/api/ScrapApi.java | 26 +- .../briefing/scrap/api/ScrapConverter.java | 9 +- .../application/ScrapCommandService.java | 29 +- .../scrap/application/ScrapQueryService.java | 10 +- .../scrap/application/dto/ScrapResponse.java | 13 +- .../java/briefing/scrap/domain/Scrap.java | 15 +- .../domain/repository/ScrapRepository.java | 7 +- .../scrap/exception/ScrapException.java | 2 +- .../security/config/SecurityConfig.java | 114 ++++--- .../security/filter/JwtRequestFilter.java | 23 +- .../handler/JwtAccessDeniedHandler.java | 41 +-- .../handler/JwtAuthenticationEntryPoint.java | 37 +- .../JwtAuthenticationExceptionHandler.java | 36 +- .../handler/annotation/AuthMember.java | 3 +- .../annotation/AuthUserArgumentResolver.java | 32 +- .../security/provider/TokenProvider.java | 102 +++--- .../annotation/CheckSameMember.java | 9 +- .../validator/CheckSameMemberValidator.java | 25 +- .../briefing/BriefingApplicationTests.java | 6 +- .../application/BriefingQueryServiceTest.java | 204 ++++++----- .../ChattingCommandServiceTest.java | 316 +++++++++--------- 109 files changed, 1925 insertions(+), 1631 deletions(-) diff --git a/build.gradle b/build.gradle index 58cdea0..a209aaa 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.1.2' id 'io.spring.dependency-management' version '1.1.2' -// id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' + id 'com.diffplug.spotless' version '6.23.3' // Spotless ํ”Œ๋Ÿฌ๊ทธ์ธ } group = 'briefing.info' @@ -72,6 +72,35 @@ tasks.named('test') { useJUnitPlatform() } + +/* + compileJava ํƒœ์Šคํฌ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— spotlessApply ํƒœ์Šคํฌ๊ฐ€ ๋จผ์ € ์‹คํ–‰๋˜๋„๋ก ๊ตฌ์„ฑ + ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ณ„๋„๋กœ ์ฝ”๋“œ ํฌ๋งทํŒ…์„ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†์ด, ์ฝ”๋“œ๊ฐ€ ํ•ญ์ƒ ์ผ๊ด€๋œ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•˜๋„๋ก ์„ค์ • + */ +tasks.named('compileJava') { + dependsOn 'spotlessApply' +} + jar { enabled = false +} + +spotless { + java { + // Google Java ํฌ๋งท ์ ์šฉ + /* + googleJavaFormat() : ํƒญ์€ 2๊ฐœ์˜ ๊ณต๋ฐฑ + googleJavaFormat().aosp() : ํƒญ์€ 4๊ฐœ์˜ ๊ณต๋ฐฑ + [์ฐธ๊ณ ] https://github.com/google/google-java-format/issues/525 + */ + googleJavaFormat().aosp() + // ์•„๋ž˜ ์ˆœ์„œ๋กœ import๋ฌธ ์ •๋ ฌ + importOrder('java', 'javax', 'jakarta', 'org', 'com') + // ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” import ์ œ๊ฑฐ + removeUnusedImports() + // ๊ฐ ๋ผ์ธ ๋์— ์žˆ๋Š” ๊ณต๋ฐฑ์„ ์ œ๊ฑฐ + trimTrailingWhitespace() + // ํŒŒ์ผ ๋์— ์ƒˆ๋กœ์šด ๋ผ์ธ ์ถ”๊ฐ€ + endWithNewline() + } } \ No newline at end of file diff --git a/src/main/java/briefing/BriefingApplication.java b/src/main/java/briefing/BriefingApplication.java index c78eec0..402c52b 100644 --- a/src/main/java/briefing/BriefingApplication.java +++ b/src/main/java/briefing/BriefingApplication.java @@ -13,8 +13,7 @@ @ImportAutoConfiguration({FeignAutoConfiguration.class}) public class BriefingApplication { - public static void main(final String[] args) { - SpringApplication.run(BriefingApplication.class, args); - } - + public static void main(final String[] args) { + SpringApplication.run(BriefingApplication.class, args); + } } diff --git a/src/main/java/briefing/GlobalControllerAdvice.java b/src/main/java/briefing/GlobalControllerAdvice.java index 5778159..3d99ae0 100644 --- a/src/main/java/briefing/GlobalControllerAdvice.java +++ b/src/main/java/briefing/GlobalControllerAdvice.java @@ -1,27 +1,27 @@ package briefing; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; + import briefing.base.BaseException; import briefing.base.BaseExceptionType; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; @Slf4j -//@RestControllerAdvice +// @RestControllerAdvice public class GlobalControllerAdvice { - @ExceptionHandler(BaseException.class) - public ResponseEntity handleException(final BaseException e) { - final BaseExceptionType type = e.exceptionType(); - log.warn("[WARN] MESSAGE: {}", type.message()); - return new ResponseEntity<>(ExceptionResponse.from(e), e.exceptionType().status()); - } + @ExceptionHandler(BaseException.class) + public ResponseEntity handleException(final BaseException e) { + final BaseExceptionType type = e.exceptionType(); + log.warn("[WARN] MESSAGE: {}", type.message()); + return new ResponseEntity<>(ExceptionResponse.from(e), e.exceptionType().status()); + } - private record ExceptionResponse(String message) { + private record ExceptionResponse(String message) { - private static ExceptionResponse from(final BaseException e) { - return new ExceptionResponse(e.exceptionType().message()); + private static ExceptionResponse from(final BaseException e) { + return new ExceptionResponse(e.exceptionType().message()); + } } - } } diff --git a/src/main/java/briefing/base/BaseDateTimeEntity.java b/src/main/java/briefing/base/BaseDateTimeEntity.java index a645f6d..1436ad7 100644 --- a/src/main/java/briefing/base/BaseDateTimeEntity.java +++ b/src/main/java/briefing/base/BaseDateTimeEntity.java @@ -1,22 +1,25 @@ package briefing.base; +import java.time.LocalDateTime; + import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; -import java.time.LocalDateTime; -import lombok.Getter; + import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import lombok.Getter; + @EntityListeners(AuditingEntityListener.class) @MappedSuperclass @Getter public abstract class BaseDateTimeEntity { - @Column(updatable = false) - @CreatedDate - private LocalDateTime createdAt; - @LastModifiedDate - private LocalDateTime updatedAt; + @Column(updatable = false) + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate private LocalDateTime updatedAt; } diff --git a/src/main/java/briefing/base/BaseException.java b/src/main/java/briefing/base/BaseException.java index 98bfa84..35eec45 100644 --- a/src/main/java/briefing/base/BaseException.java +++ b/src/main/java/briefing/base/BaseException.java @@ -2,9 +2,9 @@ public abstract class BaseException extends RuntimeException { - protected BaseException(final String message) { - super(message); - } + protected BaseException(final String message) { + super(message); + } - public abstract BaseExceptionType exceptionType(); + public abstract BaseExceptionType exceptionType(); } diff --git a/src/main/java/briefing/base/BaseExceptionType.java b/src/main/java/briefing/base/BaseExceptionType.java index 49d6b86..8081bed 100644 --- a/src/main/java/briefing/base/BaseExceptionType.java +++ b/src/main/java/briefing/base/BaseExceptionType.java @@ -4,7 +4,7 @@ public interface BaseExceptionType { - HttpStatus status(); + HttpStatus status(); - String message(); + String message(); } diff --git a/src/main/java/briefing/briefing/api/BriefingApi.java b/src/main/java/briefing/briefing/api/BriefingApi.java index 5847219..f82b668 100644 --- a/src/main/java/briefing/briefing/api/BriefingApi.java +++ b/src/main/java/briefing/briefing/api/BriefingApi.java @@ -1,112 +1,121 @@ package briefing.briefing.api; -import briefing.briefing.application.BriefingCommandService; -import briefing.briefing.application.BriefingQueryService; -import briefing.briefing.application.dto.*; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.BriefingType; import java.time.LocalDate; import java.util.Arrays; import java.util.List; import java.util.Optional; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import briefing.briefing.application.BriefingCommandService; +import briefing.briefing.application.BriefingQueryService; +import briefing.briefing.application.dto.*; +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.BriefingType; import briefing.common.enums.APIVersion; import briefing.common.response.CommonResponse; import briefing.member.domain.Member; import briefing.scrap.application.ScrapQueryService; import briefing.security.handler.annotation.AuthMember; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; - import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; -@Tag(name = "03-Briefing \uD83D\uDCF0",description = "๋ธŒ๋ฆฌํ•‘ ๊ด€๋ จ API") +@Tag(name = "03-Briefing \uD83D\uDCF0", description = "๋ธŒ๋ฆฌํ•‘ ๊ด€๋ จ API") @RestController @RequiredArgsConstructor public class BriefingApi { - private final BriefingQueryService briefingQueryService; - private final BriefingCommandService briefingCommandService; - private final ScrapQueryService scrapQueryService; - - @GetMapping("/v2/briefings") - @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V2", description = "") - public CommonResponse findBriefingsV2( - @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params - ) { - List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2); - return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewListDTOV2(params.getDate(), briefingList)); - } - - @GetMapping("/briefings") - @Parameter(name = "timeOfDay", hidden = true) - @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V1", description = "") - public CommonResponse findBriefings( - @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params - ) { - - List briefingList = briefingQueryService.findBriefings(params, APIVersion.V1); - return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewListDTO(params.getDate(), briefingList)); - } - - @Deprecated - @Operation(summary = "ํ‚ค์›Œ๋“œ ์ „๋‹ฌ V2 ์ž„์‹œ API", description = "ํ‚ค์›Œ๋“œ ์ „๋‹ฌ V2 ์ž„์‹œ API ์ž…๋‹ˆ๋‹ค. ์‘๋‹ต์€ ๋ฌด์กฐ๊ฑด ๋™์ผํ•ฉ๋‹ˆ๋‹ค. type๋งŒ ์ฃผ์‹ ๊ฑธ ๋‹ด์•„์„œ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.") - @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต") - @GetMapping("/briefings/temp") - public CommonResponse findBriefingsV2Temp( - @RequestParam("type") final BriefingType type, - @RequestParam("date") final LocalDate date - ){ - List idList = Arrays.asList(346L, 347L, 348L, 349L, 350L); - return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewV2TempListDTO(date,idList,type)); - } - - @GetMapping("/v2/briefings/{id}") - @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V2", description = "") - @Parameter(name = "member", hidden = true) - public CommonResponse findBriefingV2( - @PathVariable final Long id, - @AuthMember Member member - ) { - - Boolean isScrap = Optional.ofNullable(member) - .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) - .orElseGet(() -> Boolean.FALSE); - - Boolean isBriefingOpen = false; - Boolean isWarning = false; - - return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTOV2(briefingQueryService.findBriefing(id, APIVersion.V2), isScrap, isBriefingOpen, isWarning)); - } - - @GetMapping("/briefings/{id}") - @Parameter(name = "member", hidden = true) - @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V1", description = "") - public CommonResponse findBriefing( - @PathVariable final Long id, - @AuthMember Member member - ) { - - Boolean isScrap = Optional.ofNullable(member) - .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) - .orElseGet(() -> Boolean.FALSE); - - Boolean isBriefingOpen = false; - Boolean isWarning = false; - - return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTO(briefingQueryService.findBriefing(id, APIVersion.V1), isScrap, isBriefingOpen, isWarning)); - } - - @PostMapping("/briefings") - @ResponseStatus(HttpStatus.CREATED) - @Operation(summary = "03-03Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋“ฑ๋ก", description = "") - public void createBriefing(@RequestBody final BriefingRequestDTO.BriefingCreate request) { - briefingCommandService.createBriefing(request); - } + private final BriefingQueryService briefingQueryService; + private final BriefingCommandService briefingCommandService; + private final ScrapQueryService scrapQueryService; + + @GetMapping("/v2/briefings") + @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V2", description = "") + public CommonResponse findBriefingsV2( + @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params) { + List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2); + return CommonResponse.onSuccess( + BriefingConverter.toBriefingPreviewListDTOV2(params.getDate(), briefingList)); + } + + @GetMapping("/briefings") + @Parameter(name = "timeOfDay", hidden = true) + @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V1", description = "") + public CommonResponse findBriefings( + @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params) { + + List briefingList = briefingQueryService.findBriefings(params, APIVersion.V1); + return CommonResponse.onSuccess( + BriefingConverter.toBriefingPreviewListDTO(params.getDate(), briefingList)); + } + + @Deprecated + @Operation( + summary = "ํ‚ค์›Œ๋“œ ์ „๋‹ฌ V2 ์ž„์‹œ API", + description = "ํ‚ค์›Œ๋“œ ์ „๋‹ฌ V2 ์ž„์‹œ API ์ž…๋‹ˆ๋‹ค. ์‘๋‹ต์€ ๋ฌด์กฐ๊ฑด ๋™์ผํ•ฉ๋‹ˆ๋‹ค. type๋งŒ ์ฃผ์‹ ๊ฑธ ๋‹ด์•„์„œ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.") + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต") + @GetMapping("/briefings/temp") + public CommonResponse findBriefingsV2Temp( + @RequestParam("type") final BriefingType type, + @RequestParam("date") final LocalDate date) { + List idList = Arrays.asList(346L, 347L, 348L, 349L, 350L); + return CommonResponse.onSuccess( + BriefingConverter.toBriefingPreviewV2TempListDTO(date, idList, type)); + } + + @GetMapping("/v2/briefings/{id}") + @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V2", description = "") + @Parameter(name = "member", hidden = true) + public CommonResponse findBriefingV2( + @PathVariable final Long id, @AuthMember Member member) { + + Boolean isScrap = + Optional.ofNullable(member) + .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) + .orElseGet(() -> Boolean.FALSE); + + Boolean isBriefingOpen = false; + Boolean isWarning = false; + + return CommonResponse.onSuccess( + BriefingConverter.toBriefingDetailDTOV2( + briefingQueryService.findBriefing(id, APIVersion.V2), + isScrap, + isBriefingOpen, + isWarning)); + } + + @GetMapping("/briefings/{id}") + @Parameter(name = "member", hidden = true) + @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V1", description = "") + public CommonResponse findBriefing( + @PathVariable final Long id, @AuthMember Member member) { + + Boolean isScrap = + Optional.ofNullable(member) + .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) + .orElseGet(() -> Boolean.FALSE); + + Boolean isBriefingOpen = false; + Boolean isWarning = false; + + return CommonResponse.onSuccess( + BriefingConverter.toBriefingDetailDTO( + briefingQueryService.findBriefing(id, APIVersion.V1), + isScrap, + isBriefingOpen, + isWarning)); + } + + @PostMapping("/briefings") + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "03-03Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋“ฑ๋ก", description = "") + public void createBriefing(@RequestBody final BriefingRequestDTO.BriefingCreate request) { + briefingCommandService.createBriefing(request); + } } diff --git a/src/main/java/briefing/briefing/api/BriefingConverter.java b/src/main/java/briefing/briefing/api/BriefingConverter.java index f192e74..81d7b79 100644 --- a/src/main/java/briefing/briefing/api/BriefingConverter.java +++ b/src/main/java/briefing/briefing/api/BriefingConverter.java @@ -1,20 +1,19 @@ package briefing.briefing.api; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + import briefing.briefing.application.dto.BriefingRequestDTO; import briefing.briefing.application.dto.BriefingResponseDTO; import briefing.briefing.domain.Article; import briefing.briefing.domain.Briefing; import briefing.briefing.domain.BriefingType; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - - public class BriefingConverter { - public static BriefingResponseDTO.BriefingPreviewDTOV2 toBriefingPreviewDTOV2(Briefing briefing){ + public static BriefingResponseDTO.BriefingPreviewDTOV2 toBriefingPreviewDTOV2( + Briefing briefing) { return BriefingResponseDTO.BriefingPreviewDTOV2.builder() .id(briefing.getId()) .ranks(briefing.getRanks()) @@ -24,7 +23,7 @@ public static BriefingResponseDTO.BriefingPreviewDTOV2 toBriefingPreviewDTOV2(Br .build(); } - public static BriefingResponseDTO.BriefingPreviewDTO toBriefingPreviewDTO(Briefing briefing){ + public static BriefingResponseDTO.BriefingPreviewDTO toBriefingPreviewDTO(Briefing briefing) { return BriefingResponseDTO.BriefingPreviewDTO.builder() .id(briefing.getId()) .ranks(briefing.getRanks()) @@ -33,19 +32,21 @@ public static BriefingResponseDTO.BriefingPreviewDTO toBriefingPreviewDTO(Briefi .build(); } - private static LocalDateTime getPreviewListDTOCreatedAt(final LocalDate date, List briefingList) { - if(!briefingList.isEmpty()) { + private static LocalDateTime getPreviewListDTOCreatedAt( + final LocalDate date, List briefingList) { + if (!briefingList.isEmpty()) { return briefingList.get(0).getCreatedAt(); } - if(date != null) { - return date.atTime(3,0); + if (date != null) { + return date.atTime(3, 0); } return LocalDateTime.now(); } - public static BriefingResponseDTO.BriefingPreviewListDTOV2 toBriefingPreviewListDTOV2(final LocalDate date, List briefingList){ - final List briefingPreviewDTOList = briefingList.stream() - .map(BriefingConverter::toBriefingPreviewDTOV2).toList(); + public static BriefingResponseDTO.BriefingPreviewListDTOV2 toBriefingPreviewListDTOV2( + final LocalDate date, List briefingList) { + final List briefingPreviewDTOList = + briefingList.stream().map(BriefingConverter::toBriefingPreviewDTOV2).toList(); return BriefingResponseDTO.BriefingPreviewListDTOV2.builder() .createdAt(getPreviewListDTOCreatedAt(date, briefingList)) @@ -53,9 +54,10 @@ public static BriefingResponseDTO.BriefingPreviewListDTOV2 toBriefingPreviewList .build(); } - public static BriefingResponseDTO.BriefingPreviewListDTO toBriefingPreviewListDTO(final LocalDate date, List briefingList){ - final List briefingPreviewDTOList = briefingList.stream() - .map(BriefingConverter::toBriefingPreviewDTO).toList(); + public static BriefingResponseDTO.BriefingPreviewListDTO toBriefingPreviewListDTO( + final LocalDate date, List briefingList) { + final List briefingPreviewDTOList = + briefingList.stream().map(BriefingConverter::toBriefingPreviewDTO).toList(); return BriefingResponseDTO.BriefingPreviewListDTO.builder() .createdAt(getPreviewListDTOCreatedAt(date, briefingList)) @@ -63,7 +65,8 @@ public static BriefingResponseDTO.BriefingPreviewListDTO toBriefingPreviewListDT .build(); } - public static BriefingResponseDTO.ArticleResponseDTO toArticleResponseDTO(final Article article){ + public static BriefingResponseDTO.ArticleResponseDTO toArticleResponseDTO( + final Article article) { return BriefingResponseDTO.ArticleResponseDTO.builder() .id(article.getId()) .press(article.getPress()) @@ -73,14 +76,12 @@ public static BriefingResponseDTO.ArticleResponseDTO toArticleResponseDTO(final } public static BriefingResponseDTO.BriefingDetailDTO toBriefingDetailDTO( - Briefing briefing, - Boolean isScrap, - Boolean isBriefingOpen, - Boolean isWarning - ){ + Briefing briefing, Boolean isScrap, Boolean isBriefingOpen, Boolean isWarning) { - List articleResponseDTOList = briefing.getBriefingArticles().stream() - .map(article -> toArticleResponseDTO(article.getArticle())).toList(); + List articleResponseDTOList = + briefing.getBriefingArticles().stream() + .map(article -> toArticleResponseDTO(article.getArticle())) + .toList(); return BriefingResponseDTO.BriefingDetailDTO.builder() .id(briefing.getId()) @@ -97,14 +98,12 @@ public static BriefingResponseDTO.BriefingDetailDTO toBriefingDetailDTO( } public static BriefingResponseDTO.BriefingDetailDTOV2 toBriefingDetailDTOV2( - Briefing briefing, - Boolean isScrap, - Boolean isBriefingOpen, - Boolean isWarning - ){ + Briefing briefing, Boolean isScrap, Boolean isBriefingOpen, Boolean isWarning) { - List articleResponseDTOList = briefing.getBriefingArticles().stream() - .map(article -> toArticleResponseDTO(article.getArticle())).toList(); + List articleResponseDTOList = + briefing.getBriefingArticles().stream() + .map(article -> toArticleResponseDTO(article.getArticle())) + .toList(); return BriefingResponseDTO.BriefingDetailDTOV2.builder() .id(briefing.getId()) @@ -124,7 +123,7 @@ public static BriefingResponseDTO.BriefingDetailDTOV2 toBriefingDetailDTOV2( .build(); } - public static Briefing toBriefing(BriefingRequestDTO.BriefingCreate request){ + public static Briefing toBriefing(BriefingRequestDTO.BriefingCreate request) { return Briefing.builder() .type(request.getBriefingType()) .ranks(request.getRanks()) @@ -136,7 +135,7 @@ public static Briefing toBriefing(BriefingRequestDTO.BriefingCreate request){ .build(); } - public static Article toArticle(BriefingRequestDTO.ArticleCreateDTO request){ + public static Article toArticle(BriefingRequestDTO.ArticleCreateDTO request) { return Article.builder() .press(request.getPress()) .title(request.getTitle()) @@ -144,35 +143,33 @@ public static Article toArticle(BriefingRequestDTO.ArticleCreateDTO request){ .build(); } - - public static BriefingResponseDTO.BriefingPreviewV2TempDTO toBriefingPreviewV2TempDTO(Long id){ + public static BriefingResponseDTO.BriefingPreviewV2TempDTO toBriefingPreviewV2TempDTO(Long id) { Integer rank = null; String title = null; String subTitle = null; Integer scrapCount = null; - if (id.equals(346L)){ + if (id.equals(346L)) { rank = 1; title = "์†Œ์…œ 1"; subTitle = "๋ธŒ๋ฆฌํ•‘ ๋ถ€์ œ๋ชฉ 1"; scrapCount = 1234; - } - else if (id.equals(347L)){ + } else if (id.equals(347L)) { rank = 2; title = "์†Œ์…œ 2"; subTitle = "๋ธŒ๋ฆฌํ•‘ ๋ถ€์ œ๋ชฉ 2"; scrapCount = 123; - }else if(id.equals(348L)){ + } else if (id.equals(348L)) { rank = 3; title = "์†Œ์…œ 3"; subTitle = "๋ธŒ๋ฆฌํ•‘ ๋ถ€์ œ๋ชฉ 3"; scrapCount = 13; - }else if (id.equals(349L)){ + } else if (id.equals(349L)) { rank = 4; title = "์†Œ์…œ 4"; subTitle = "๋ธŒ๋ฆฌํ•‘ ๋ถ€์ œ๋ชฉ 4"; scrapCount = 12323; - }else if (id.equals(350L)){ + } else if (id.equals(350L)) { rank = 5; title = "์†Œ์…œ 5"; subTitle = "๋ธŒ๋ฆฌํ•‘ ๋ถ€์ œ๋ชฉ 5"; @@ -188,14 +185,13 @@ else if (id.equals(347L)){ .build(); } - public static BriefingResponseDTO.BriefingV2PreviewListDTO toBriefingPreviewV2TempListDTO(final LocalDate date, List temp, BriefingType briefingType){ - List tempDTOList = temp.stream() - .map( - BriefingConverter::toBriefingPreviewV2TempDTO - ).toList(); + public static BriefingResponseDTO.BriefingV2PreviewListDTO toBriefingPreviewV2TempListDTO( + final LocalDate date, List temp, BriefingType briefingType) { + List tempDTOList = + temp.stream().map(BriefingConverter::toBriefingPreviewV2TempDTO).toList(); return BriefingResponseDTO.BriefingV2PreviewListDTO.builder() - .createdAt(date.atTime(3,0)) + .createdAt(date.atTime(3, 0)) .type(briefingType.getValue()) .briefings(tempDTOList) .build(); diff --git a/src/main/java/briefing/briefing/application/BriefingCommandService.java b/src/main/java/briefing/briefing/application/BriefingCommandService.java index 80cdcca..bcfac21 100644 --- a/src/main/java/briefing/briefing/application/BriefingCommandService.java +++ b/src/main/java/briefing/briefing/application/BriefingCommandService.java @@ -1,5 +1,10 @@ package briefing.briefing.application; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.briefing.api.BriefingConverter; import briefing.briefing.application.dto.BriefingRequestDTO; import briefing.briefing.domain.Article; @@ -8,32 +13,27 @@ import briefing.briefing.domain.repository.ArticleRepository; import briefing.briefing.domain.repository.BriefingArticleRepository; import briefing.briefing.domain.repository.BriefingRepository; -import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @Transactional @RequiredArgsConstructor public class BriefingCommandService { - private final BriefingRepository briefingRepository; - private final ArticleRepository articleRepository; - private final BriefingArticleRepository briefingArticleRepository; + private final BriefingRepository briefingRepository; + private final ArticleRepository articleRepository; + private final BriefingArticleRepository briefingArticleRepository; - public void createBriefing(final BriefingRequestDTO.BriefingCreate request) { - final Briefing briefing = BriefingConverter.toBriefing(request); - final List
articles = request.getArticles().stream() - .map(BriefingConverter::toArticle) - .toList(); + public void createBriefing(final BriefingRequestDTO.BriefingCreate request) { + final Briefing briefing = BriefingConverter.toBriefing(request); + final List
articles = + request.getArticles().stream().map(BriefingConverter::toArticle).toList(); - briefingRepository.save(briefing); - articleRepository.saveAll(articles); + briefingRepository.save(briefing); + articleRepository.saveAll(articles); - final List briefingArticles = articles.stream() - .map(article -> new BriefingArticle(briefing, article)) - .toList(); - briefingArticleRepository.saveAll(briefingArticles); - } + final List briefingArticles = + articles.stream().map(article -> new BriefingArticle(briefing, article)).toList(); + briefingArticleRepository.saveAll(briefingArticles); + } } diff --git a/src/main/java/briefing/briefing/application/BriefingQueryService.java b/src/main/java/briefing/briefing/application/BriefingQueryService.java index 0c1e9aa..931245a 100644 --- a/src/main/java/briefing/briefing/application/BriefingQueryService.java +++ b/src/main/java/briefing/briefing/application/BriefingQueryService.java @@ -1,33 +1,38 @@ package briefing.briefing.application; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.briefing.application.context.BriefingQueryContext; import briefing.briefing.application.context.BriefingQueryContextFactory; import briefing.briefing.application.dto.BriefingRequestParam; import briefing.briefing.domain.Briefing; -import java.util.List; - import briefing.common.enums.APIVersion; import briefing.exception.ErrorCode; import briefing.exception.handler.BriefingException; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class BriefingQueryService { - private final BriefingQueryContextFactory briefingQueryContextFactory; + private final BriefingQueryContextFactory briefingQueryContextFactory; - public List findBriefings(BriefingRequestParam.BriefingPreviewListParam params, APIVersion version) { - BriefingQueryContext briefingQueryContext = briefingQueryContextFactory.getContextByVersion(version); - return briefingQueryContext.findBriefings(params); - } + public List findBriefings( + BriefingRequestParam.BriefingPreviewListParam params, APIVersion version) { + BriefingQueryContext briefingQueryContext = + briefingQueryContextFactory.getContextByVersion(version); + return briefingQueryContext.findBriefings(params); + } - public Briefing findBriefing(final Long id, final APIVersion version) { - BriefingQueryContext briefingQueryContext = briefingQueryContextFactory.getContextByVersion(version); - return briefingQueryContext.findById(id) - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); - } + public Briefing findBriefing(final Long id, final APIVersion version) { + BriefingQueryContext briefingQueryContext = + briefingQueryContextFactory.getContextByVersion(version); + return briefingQueryContext + .findById(id) + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + } } diff --git a/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java b/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java index 896be83..75f62d5 100644 --- a/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java +++ b/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java @@ -1,13 +1,13 @@ package briefing.briefing.application.context; +import java.util.List; +import java.util.Optional; + import briefing.briefing.application.dto.BriefingRequestParam; import briefing.briefing.application.strategy.BriefingQueryStrategy; import briefing.briefing.domain.Briefing; import lombok.RequiredArgsConstructor; -import java.util.List; -import java.util.Optional; - @RequiredArgsConstructor public class BriefingQueryContext { private final BriefingQueryStrategy briefingQueryStrategy; diff --git a/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java b/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java index 2becefc..ab44830 100644 --- a/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java +++ b/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java @@ -1,19 +1,15 @@ package briefing.briefing.application.context; -import briefing.briefing.application.strategy.BriefingQueryStrategy; -import briefing.briefing.application.strategy.BriefingV1QueryStrategy; -import briefing.briefing.application.strategy.BriefingV2QueryStrategy; -import briefing.briefing.domain.repository.BriefingRepository; -import briefing.common.enums.APIVersion; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import briefing.briefing.application.strategy.BriefingQueryStrategy; +import briefing.common.enums.APIVersion; + @Component public class BriefingQueryContextFactory { @@ -36,4 +32,3 @@ public BriefingQueryContext getContextByVersion(APIVersion version) { return context; } } - diff --git a/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java index a2389fa..794d524 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java @@ -1,28 +1,36 @@ package briefing.briefing.application.dto; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + import briefing.briefing.domain.BriefingType; import briefing.briefing.domain.TimeOfDay; import briefing.chatting.domain.GptModel; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import java.util.List; - public class BriefingRequestDTO { @Getter - public static class ArticleCreateDTO{ + public static class ArticleCreateDTO { String press; String title; String url; } @Getter - public static class BriefingCreate{ - @JsonProperty("id") Integer ranks; - @JsonProperty("keyword") String title; + public static class BriefingCreate { + @JsonProperty("id") + Integer ranks; + + @JsonProperty("keyword") + String title; + String subtitle; - @JsonProperty("context") String content; + + @JsonProperty("context") + String content; + List articles; GptModel gptModel = GptModel.GPT_4; TimeOfDay timeOfDay = TimeOfDay.MORNING; diff --git a/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java b/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java index 196837f..90a5069 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java @@ -1,22 +1,23 @@ package briefing.briefing.application.dto; +import java.time.LocalDate; + +import jakarta.validation.constraints.NotNull; + import briefing.briefing.domain.BriefingType; import briefing.briefing.domain.TimeOfDay; -import jakarta.validation.constraints.NotNull; import lombok.*; -import java.time.LocalDate; - @NoArgsConstructor(access = AccessLevel.PRIVATE) public class BriefingRequestParam { @Builder - @Getter @Setter + @Getter + @Setter @NoArgsConstructor @AllArgsConstructor public static class BriefingPreviewListParam { - @NotNull - private BriefingType type; + @NotNull private BriefingType type; private LocalDate date; private TimeOfDay timeOfDay = TimeOfDay.MORNING; diff --git a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java index 41876dc..9f4b7bd 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java @@ -1,5 +1,9 @@ package briefing.briefing.application.dto; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + import briefing.briefing.domain.BriefingType; import briefing.briefing.domain.TimeOfDay; import briefing.chatting.domain.GptModel; @@ -8,17 +12,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - public class BriefingResponseDTO { @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class ArticleResponseDTO{ + public static class ArticleResponseDTO { Long id; String press; String title; @@ -29,20 +29,19 @@ public static class ArticleResponseDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingPreviewDTOV2{ + public static class BriefingPreviewDTOV2 { Long id; Integer ranks; String title; String subtitle; - @Builder.Default - Integer scrapCount = 0; + @Builder.Default Integer scrapCount = 0; } @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingPreviewDTO{ + public static class BriefingPreviewDTO { Long id; Integer ranks; String title; @@ -53,7 +52,7 @@ public static class BriefingPreviewDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingDetailDTO{ + public static class BriefingDetailDTO { Long id; Integer ranks; String title; @@ -70,7 +69,7 @@ public static class BriefingDetailDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingDetailDTOV2{ + public static class BriefingDetailDTOV2 { Long id; Integer ranks; String title; @@ -81,21 +80,17 @@ public static class BriefingDetailDTOV2{ Boolean isScrap; Boolean isBriefingOpen; Boolean isWarning; - @Builder.Default - Integer scrapCount = 0; - @Builder.Default - GptModel gptModel = GptModel.GPT_3_5_TURBO; - @Builder.Default - TimeOfDay timeOfDay = TimeOfDay.MORNING; - @Builder.Default - BriefingType type = BriefingType.KOREA; + @Builder.Default Integer scrapCount = 0; + @Builder.Default GptModel gptModel = GptModel.GPT_3_5_TURBO; + @Builder.Default TimeOfDay timeOfDay = TimeOfDay.MORNING; + @Builder.Default BriefingType type = BriefingType.KOREA; } @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingPreviewListDTOV2{ + public static class BriefingPreviewListDTOV2 { LocalDateTime createdAt; List briefings; } @@ -104,7 +99,7 @@ public static class BriefingPreviewListDTOV2{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingPreviewListDTO{ + public static class BriefingPreviewListDTO { LocalDateTime createdAt; List briefings; } @@ -113,7 +108,7 @@ public static class BriefingPreviewListDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingV2PreviewListDTO{ + public static class BriefingV2PreviewListDTO { LocalDateTime createdAt; String type; List briefings; @@ -123,7 +118,7 @@ public static class BriefingV2PreviewListDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class BriefingPreviewV2TempDTO{ + public static class BriefingPreviewV2TempDTO { Long id; Integer ranks; String title; diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java index 1669534..8c24c33 100644 --- a/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java +++ b/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java @@ -1,12 +1,12 @@ package briefing.briefing.application.strategy; +import java.util.List; +import java.util.Optional; + import briefing.briefing.application.dto.BriefingRequestParam; import briefing.briefing.domain.Briefing; import briefing.common.enums.APIVersion; -import java.util.List; -import java.util.Optional; - public interface BriefingQueryStrategy { List findBriefings(BriefingRequestParam.BriefingPreviewListParam params); diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java index 89ae271..2bc3ee3 100644 --- a/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java +++ b/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java @@ -1,18 +1,19 @@ package briefing.briefing.application.strategy; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Component; + import briefing.briefing.application.dto.BriefingRequestParam; import briefing.briefing.domain.Briefing; import briefing.briefing.domain.BriefingType; import briefing.briefing.domain.repository.BriefingRepository; import briefing.common.enums.APIVersion; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Collections; -import java.util.List; -import java.util.Optional; @Component @RequiredArgsConstructor @@ -25,9 +26,12 @@ public List findBriefings(BriefingRequestParam.BriefingPreviewListPara final LocalDateTime startDateTime = params.getDate().atStartOfDay(); final LocalDateTime endDateTime = params.getDate().atTime(LocalTime.MAX); - List briefingList = briefingRepository.findAllByTypeAndCreatedAtBetweenOrderByRanks(params.getType(), startDateTime, endDateTime); - if(briefingList.isEmpty()) { - briefingList = briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(BriefingType.SOCIAL); + List briefingList = + briefingRepository.findAllByTypeAndCreatedAtBetweenOrderByRanks( + params.getType(), startDateTime, endDateTime); + if (briefingList.isEmpty()) { + briefingList = + briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(BriefingType.SOCIAL); Collections.reverse(briefingList); } return briefingList; diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java index 1f44edc..ce35ca7 100644 --- a/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java +++ b/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java @@ -1,35 +1,36 @@ package briefing.briefing.application.strategy; -import briefing.briefing.application.dto.BriefingRequestParam; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.repository.BriefingRepository; -import briefing.common.enums.APIVersion; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import org.springframework.stereotype.Component; + +import briefing.briefing.application.dto.BriefingRequestParam; +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.repository.BriefingRepository; +import briefing.common.enums.APIVersion; +import lombok.RequiredArgsConstructor; + @Component @RequiredArgsConstructor -public class BriefingV2QueryStrategy implements BriefingQueryStrategy{ +public class BriefingV2QueryStrategy implements BriefingQueryStrategy { private final BriefingRepository briefingRepository; @Override public List findBriefings(BriefingRequestParam.BriefingPreviewListParam params) { List briefingList; - if(params.isPresentDate()) { + if (params.isPresentDate()) { final LocalDateTime startDateTime = params.getDate().atStartOfDay(); final LocalDateTime endDateTime = params.getDate().atTime(LocalTime.MAX); - briefingList = briefingRepository.findBriefingsWithScrapCount( - params.getType(), startDateTime, endDateTime, params.getTimeOfDay()); - if(!briefingList.isEmpty()) return briefingList; + briefingList = + briefingRepository.findBriefingsWithScrapCount( + params.getType(), startDateTime, endDateTime, params.getTimeOfDay()); + if (!briefingList.isEmpty()) return briefingList; } briefingList = briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(params.getType()); diff --git a/src/main/java/briefing/briefing/domain/Article.java b/src/main/java/briefing/briefing/domain/Article.java index 3a13dcb..ccfc93d 100644 --- a/src/main/java/briefing/briefing/domain/Article.java +++ b/src/main/java/briefing/briefing/domain/Article.java @@ -1,32 +1,37 @@ package briefing.briefing.domain; -import briefing.base.BaseDateTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; + +import briefing.base.BaseDateTimeEntity; import lombok.*; @Entity -@Getter @Builder +@Getter +@Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Article extends BaseDateTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @Column(nullable = false) - private String press; - @Column(nullable = false) - private String title; - @Column(nullable = false, length = 1024) - private String url; - -// public Article(final String press, final String title, final String url) { -// this.press = press; -// this.title = title; -// this.url = url; -// } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String press; + + @Column(nullable = false) + private String title; + + @Column(nullable = false, length = 1024) + private String url; + + // public Article(final String press, final String title, final String url) { + // this.press = press; + // this.title = title; + // this.url = url; + // } } diff --git a/src/main/java/briefing/briefing/domain/Briefing.java b/src/main/java/briefing/briefing/domain/Briefing.java index e875d56..e028401 100644 --- a/src/main/java/briefing/briefing/domain/Briefing.java +++ b/src/main/java/briefing/briefing/domain/Briefing.java @@ -1,57 +1,56 @@ package briefing.briefing.domain; -import briefing.base.BaseDateTimeEntity; -import briefing.chatting.domain.GptModel; -import jakarta.persistence.*; - import java.util.ArrayList; import java.util.List; +import jakarta.persistence.*; + +import briefing.base.BaseDateTimeEntity; +import briefing.chatting.domain.GptModel; import lombok.*; @Entity -@Getter @Builder +@Getter +@Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Briefing extends BaseDateTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private BriefingType type; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private BriefingType type; - @Column(nullable = false) - private Integer ranks; + @Column(nullable = false) + private Integer ranks; - @Column(nullable = false) - private String title; + @Column(nullable = false) + private String title; - @Column(nullable = false) - private String subtitle; + @Column(nullable = false) + private String subtitle; - @Column(nullable = false, length = 1000) - private String content; + @Column(nullable = false, length = 1000) + private String content; - @Builder.Default - @OneToMany(mappedBy = "briefing", fetch = FetchType.LAZY) - private List briefingArticles = new ArrayList<>(); + @Builder.Default + @OneToMany(mappedBy = "briefing", fetch = FetchType.LAZY) + private List briefingArticles = new ArrayList<>(); - @Builder.Default - @Transient - private Integer scrapCount = 0; + @Builder.Default @Transient private Integer scrapCount = 0; - @Builder.Default - @Enumerated(EnumType.STRING) - private TimeOfDay timeOfDay = TimeOfDay.MORNING; + @Builder.Default + @Enumerated(EnumType.STRING) + private TimeOfDay timeOfDay = TimeOfDay.MORNING; - @Builder.Default - @Enumerated(EnumType.STRING) - private GptModel gptModel = GptModel.GPT_3_5_TURBO; + @Builder.Default + @Enumerated(EnumType.STRING) + private GptModel gptModel = GptModel.GPT_3_5_TURBO; - public void setScrapCount(Integer scrapCount) { - this.scrapCount = scrapCount; - } + public void setScrapCount(Integer scrapCount) { + this.scrapCount = scrapCount; + } } diff --git a/src/main/java/briefing/briefing/domain/BriefingArticle.java b/src/main/java/briefing/briefing/domain/BriefingArticle.java index 9680b4e..b902ee1 100644 --- a/src/main/java/briefing/briefing/domain/BriefingArticle.java +++ b/src/main/java/briefing/briefing/domain/BriefingArticle.java @@ -1,6 +1,5 @@ package briefing.briefing.domain; -import briefing.base.BaseDateTimeEntity; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -8,6 +7,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; + +import briefing.base.BaseDateTimeEntity; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,18 +18,20 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class BriefingArticle extends BaseDateTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(nullable = false) - private Briefing briefing; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(nullable = false) - private Article article; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false) + private Briefing briefing; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false) + private Article article; - public BriefingArticle(final Briefing briefing, final Article article) { - this.briefing = briefing; - this.article = article; - } + public BriefingArticle(final Briefing briefing, final Article article) { + this.briefing = briefing; + this.article = article; + } } diff --git a/src/main/java/briefing/briefing/domain/BriefingType.java b/src/main/java/briefing/briefing/domain/BriefingType.java index 99e18de..a9bea3a 100644 --- a/src/main/java/briefing/briefing/domain/BriefingType.java +++ b/src/main/java/briefing/briefing/domain/BriefingType.java @@ -10,18 +10,18 @@ @Getter @RequiredArgsConstructor public enum BriefingType { - KOREA("Korea"), - GLOBAL("Global"), - SOCIAL("Social"), - SCIENCE("Science"), - ECONOMY("Economy"); + KOREA("Korea"), + GLOBAL("Global"), + SOCIAL("Social"), + SCIENCE("Science"), + ECONOMY("Economy"); - private final String value; + private final String value; - public static BriefingType from(final String type) { - return Arrays.stream(values()) - .filter(value -> value.getValue().equals(type)) - .findAny() - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_TYPE)); - } + public static BriefingType from(final String type) { + return Arrays.stream(values()) + .filter(value -> value.getValue().equals(type)) + .findAny() + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_TYPE)); + } } diff --git a/src/main/java/briefing/briefing/domain/TimeOfDay.java b/src/main/java/briefing/briefing/domain/TimeOfDay.java index abd3fbb..d7e2c24 100644 --- a/src/main/java/briefing/briefing/domain/TimeOfDay.java +++ b/src/main/java/briefing/briefing/domain/TimeOfDay.java @@ -1,13 +1,14 @@ package briefing.briefing.domain; +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonValue; + import briefing.exception.ErrorCode; import briefing.exception.handler.BriefingException; -import com.fasterxml.jackson.annotation.JsonValue; import lombok.AllArgsConstructor; import lombok.Getter; -import java.util.Arrays; - @Getter @AllArgsConstructor public enum TimeOfDay { diff --git a/src/main/java/briefing/briefing/domain/repository/ArticleRepository.java b/src/main/java/briefing/briefing/domain/repository/ArticleRepository.java index a695170..498c00e 100644 --- a/src/main/java/briefing/briefing/domain/repository/ArticleRepository.java +++ b/src/main/java/briefing/briefing/domain/repository/ArticleRepository.java @@ -1,9 +1,7 @@ package briefing.briefing.domain.repository; -import briefing.briefing.domain.Article; import org.springframework.data.jpa.repository.JpaRepository; -public interface ArticleRepository extends JpaRepository { - +import briefing.briefing.domain.Article; -} +public interface ArticleRepository extends JpaRepository {} diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingArticleRepository.java b/src/main/java/briefing/briefing/domain/repository/BriefingArticleRepository.java index eb2f8c9..846c688 100644 --- a/src/main/java/briefing/briefing/domain/repository/BriefingArticleRepository.java +++ b/src/main/java/briefing/briefing/domain/repository/BriefingArticleRepository.java @@ -1,8 +1,7 @@ package briefing.briefing.domain.repository; -import briefing.briefing.domain.BriefingArticle; import org.springframework.data.jpa.repository.JpaRepository; -public interface BriefingArticleRepository extends JpaRepository { +import briefing.briefing.domain.BriefingArticle; -} +public interface BriefingArticleRepository extends JpaRepository {} diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java index 3c34718..b92ac27 100644 --- a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java +++ b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java @@ -1,15 +1,16 @@ package briefing.briefing.domain.repository; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.BriefingType; -import briefing.briefing.domain.TimeOfDay; - import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.BriefingType; +import briefing.briefing.domain.TimeOfDay; + public interface BriefingCustomRepository { - List findBriefingsWithScrapCount(BriefingType type, LocalDateTime start, LocalDateTime end, TimeOfDay timeOfDay); + List findBriefingsWithScrapCount( + BriefingType type, LocalDateTime start, LocalDateTime end, TimeOfDay timeOfDay); List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type); diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java index d04bb07..b1b75d4 100644 --- a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java +++ b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java @@ -1,21 +1,22 @@ package briefing.briefing.domain.repository; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Repository; + +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; + import briefing.briefing.domain.Briefing; import briefing.briefing.domain.BriefingType; import briefing.briefing.domain.QBriefing; import briefing.briefing.domain.TimeOfDay; import briefing.scrap.domain.QScrap; -import com.querydsl.core.Tuple; -import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.ArrayList; -import java.util.Optional; -import java.util.stream.Collectors; - @Repository @RequiredArgsConstructor @@ -23,29 +24,35 @@ public class BriefingCustomRepositoryImpl implements BriefingCustomRepository { private final JPAQueryFactory queryFactory; @Override - public List findBriefingsWithScrapCount(BriefingType type, LocalDateTime start, LocalDateTime end, TimeOfDay timeOfDay) { + public List findBriefingsWithScrapCount( + BriefingType type, LocalDateTime start, LocalDateTime end, TimeOfDay timeOfDay) { QBriefing briefing = QBriefing.briefing; QScrap scrap = QScrap.scrap; // ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ Tuple๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. - List results = queryFactory.select(briefing, scrap.count()) - .from(briefing) - .leftJoin(scrap).on(scrap.briefing.eq(briefing)) - .where(briefing.type.eq(type) - .and(briefing.createdAt.between(start, end)) - .and(briefing.timeOfDay.eq(timeOfDay)) - ) - .groupBy(briefing) - .orderBy(briefing.ranks.asc()) - .fetch(); + List results = + queryFactory + .select(briefing, scrap.count()) + .from(briefing) + .leftJoin(scrap) + .on(scrap.briefing.eq(briefing)) + .where( + briefing.type + .eq(type) + .and(briefing.createdAt.between(start, end)) + .and(briefing.timeOfDay.eq(timeOfDay))) + .groupBy(briefing) + .orderBy(briefing.ranks.asc()) + .fetch(); // Tuple ๊ฒฐ๊ณผ๋ฅผ Briefing๊ณผ scrapCount๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. return results.stream() - .map(tuple -> { - Briefing b = tuple.get(briefing); - b.setScrapCount(Math.toIntExact(tuple.get(scrap.count()))); - return b; - }) + .map( + tuple -> { + Briefing b = tuple.get(briefing); + b.setScrapCount(Math.toIntExact(tuple.get(scrap.count()))); + return b; + }) .toList(); } @@ -54,37 +61,43 @@ public List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type) { QBriefing briefing = QBriefing.briefing; QScrap scrap = QScrap.scrap; - List results = queryFactory.select(briefing, scrap.count()) - .from(briefing) - .leftJoin(scrap).on(scrap.briefing.eq(briefing)) - .where(briefing.type.eq(type)) - .groupBy(briefing) - .orderBy(briefing.createdAt.desc()) - .limit(10) - .fetch(); + List results = + queryFactory + .select(briefing, scrap.count()) + .from(briefing) + .leftJoin(scrap) + .on(scrap.briefing.eq(briefing)) + .where(briefing.type.eq(type)) + .groupBy(briefing) + .orderBy(briefing.createdAt.desc()) + .limit(10) + .fetch(); return results.stream() - .map(tuple -> { - Briefing b = tuple.get(briefing); - b.setScrapCount(Math.toIntExact(tuple.get(scrap.count()))); - return b; - }) + .map( + tuple -> { + Briefing b = tuple.get(briefing); + b.setScrapCount(Math.toIntExact(tuple.get(scrap.count()))); + return b; + }) .collect(Collectors.toCollection(ArrayList::new)); } - @Override public Optional findByIdWithScrapCount(Long id) { QBriefing briefing = QBriefing.briefing; QScrap scrap = QScrap.scrap; // ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ Tuple๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. - Tuple result = queryFactory.select(briefing, scrap.count()) - .from(briefing) - .leftJoin(scrap).on(scrap.briefing.eq(briefing)) - .where(briefing.id.eq(id)) - .groupBy(briefing) - .fetchOne(); // ๋‹จ์ผ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. + Tuple result = + queryFactory + .select(briefing, scrap.count()) + .from(briefing) + .leftJoin(scrap) + .on(scrap.briefing.eq(briefing)) + .where(briefing.id.eq(id)) + .groupBy(briefing) + .fetchOne(); // ๋‹จ์ผ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. // ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด Optional.empty() ๋ฐ˜ํ™˜ if (result == null) { @@ -97,5 +110,4 @@ public Optional findByIdWithScrapCount(Long id) { return Optional.of(b); } - } diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java b/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java index 1ccae49..94a5945 100644 --- a/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java +++ b/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java @@ -1,15 +1,18 @@ package briefing.briefing.domain.repository; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.BriefingType; import java.time.LocalDateTime; import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.BriefingType; + @Repository -public interface BriefingRepository extends JpaRepository, BriefingCustomRepository { +public interface BriefingRepository + extends JpaRepository, BriefingCustomRepository { - List findAllByTypeAndCreatedAtBetweenOrderByRanks(BriefingType type, LocalDateTime start, - LocalDateTime end); + List findAllByTypeAndCreatedAtBetweenOrderByRanks( + BriefingType type, LocalDateTime start, LocalDateTime end); } diff --git a/src/main/java/briefing/chatting/api/ChattingApi.java b/src/main/java/briefing/chatting/api/ChattingApi.java index 2c622c1..d2a7e66 100644 --- a/src/main/java/briefing/chatting/api/ChattingApi.java +++ b/src/main/java/briefing/chatting/api/ChattingApi.java @@ -1,14 +1,7 @@ package briefing.chatting.api; -import briefing.chatting.application.ChattingCommandService; -import briefing.chatting.application.ChattingQueryService; -import briefing.chatting.application.dto.*; - import java.util.List; -import briefing.common.response.CommonResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -17,35 +10,51 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "04-Chatting \uD83D\uDCE8",description = "์ฑ„ํŒ… ๊ด€๋ จ API") +import briefing.chatting.application.ChattingCommandService; +import briefing.chatting.application.ChattingQueryService; +import briefing.chatting.application.dto.*; +import briefing.common.response.CommonResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "04-Chatting \uD83D\uDCE8", description = "์ฑ„ํŒ… ๊ด€๋ จ API") @RestController @RequestMapping("/chattings") @RequiredArgsConstructor public class ChattingApi { - private final ChattingQueryService chattingQueryService; - private final ChattingCommandService chattingCommandService; - - @GetMapping - public CommonResponse findChattings(@RequestParam final List ids) { - return CommonResponse.onSuccess(ChattingConverter.toChattingListResponseDTO(chattingQueryService.findChattings(ids))); - } - - @GetMapping("/{id}") - public CommonResponse findChatting(@PathVariable final Long id) { - return CommonResponse.onSuccess(ChattingConverter.toChattingDetailResponseDTO(chattingQueryService.findChatting(id))); - } - - @PostMapping - public CommonResponse createChatting() { - return CommonResponse.onSuccess(ChattingConverter.toChattingCreateResponseDTO(chattingCommandService.createChatting())); - } - - @PostMapping("/{id}") - public CommonResponse requestAnswer( - @PathVariable final Long id, - @RequestBody final ChattingRequest.AnswerRequestDTO request - ) { - return CommonResponse.onSuccess(ChattingConverter.toAnswerResponseDTO(chattingCommandService.requestAnswer(id, request))); - } + private final ChattingQueryService chattingQueryService; + private final ChattingCommandService chattingCommandService; + + @GetMapping + public CommonResponse findChattings( + @RequestParam final List ids) { + return CommonResponse.onSuccess( + ChattingConverter.toChattingListResponseDTO( + chattingQueryService.findChattings(ids))); + } + + @GetMapping("/{id}") + public CommonResponse findChatting( + @PathVariable final Long id) { + return CommonResponse.onSuccess( + ChattingConverter.toChattingDetailResponseDTO( + chattingQueryService.findChatting(id))); + } + + @PostMapping + public CommonResponse createChatting() { + return CommonResponse.onSuccess( + ChattingConverter.toChattingCreateResponseDTO( + chattingCommandService.createChatting())); + } + + @PostMapping("/{id}") + public CommonResponse requestAnswer( + @PathVariable final Long id, + @RequestBody final ChattingRequest.AnswerRequestDTO request) { + return CommonResponse.onSuccess( + ChattingConverter.toAnswerResponseDTO( + chattingCommandService.requestAnswer(id, request))); + } } diff --git a/src/main/java/briefing/chatting/api/ChattingConverter.java b/src/main/java/briefing/chatting/api/ChattingConverter.java index c7460b1..d7c6aa5 100644 --- a/src/main/java/briefing/chatting/api/ChattingConverter.java +++ b/src/main/java/briefing/chatting/api/ChattingConverter.java @@ -1,16 +1,16 @@ package briefing.chatting.api; +import java.util.List; +import java.util.Optional; + import briefing.chatting.application.dto.ChattingRequest; import briefing.chatting.application.dto.ChattingResponse; import briefing.chatting.domain.Chatting; import briefing.chatting.domain.Message; -import java.util.List; -import java.util.Optional; - public class ChattingConverter { - public static ChattingResponse.AnswerResponseDTO toAnswerResponseDTO(final Message message){ + public static ChattingResponse.AnswerResponseDTO toAnswerResponseDTO(final Message message) { return ChattingResponse.AnswerResponseDTO.builder() .id(message.getId()) .role(message.getRole()) @@ -19,7 +19,7 @@ public static ChattingResponse.AnswerResponseDTO toAnswerResponseDTO(final Messa .build(); } - public static ChattingResponse.MessageResponseDTO toMessageResponseDTO(final Message message){ + public static ChattingResponse.MessageResponseDTO toMessageResponseDTO(final Message message) { return ChattingResponse.MessageResponseDTO.builder() .id(message.getId()) .role(message.getRole()) @@ -28,16 +28,19 @@ public static ChattingResponse.MessageResponseDTO toMessageResponseDTO(final Mes .build(); } - public static ChattingResponse.ChattingDetailResponseDTO toChattingDetailResponseDTO(Chatting chatting){ - List messageResponseDTOList = chatting.getMessages().stream() - .map(ChattingConverter::toMessageResponseDTO).toList(); + public static ChattingResponse.ChattingDetailResponseDTO toChattingDetailResponseDTO( + Chatting chatting) { + List messageResponseDTOList = + chatting.getMessages().stream() + .map(ChattingConverter::toMessageResponseDTO) + .toList(); return ChattingResponse.ChattingDetailResponseDTO.builder() .id(chatting.getId()) .title(chatting.getTitle()) .build(); } - public static ChattingResponse.ChattingResponseDTO toChattingResponseDTO(Chatting chatting){ + public static ChattingResponse.ChattingResponseDTO toChattingResponseDTO(Chatting chatting) { return ChattingResponse.ChattingResponseDTO.builder() .id(chatting.getId()) .title(chatting.getTitle()) @@ -45,29 +48,32 @@ public static ChattingResponse.ChattingResponseDTO toChattingResponseDTO(Chattin .build(); } - public static ChattingResponse.ChattingListResponseDTO toChattingListResponseDTO(final ListchattingList){ + public static ChattingResponse.ChattingListResponseDTO toChattingListResponseDTO( + final List chattingList) { - List chattingResponseDTOList = chattingList.stream() - .map(ChattingConverter::toChattingResponseDTO).toList(); + List chattingResponseDTOList = + chattingList.stream().map(ChattingConverter::toChattingResponseDTO).toList(); return ChattingResponse.ChattingListResponseDTO.builder() .chattings(chattingResponseDTOList) .build(); } - public static ChattingResponse.ChattingCreateResponseDTO toChattingCreateResponseDTO(Chatting chatting){ + public static ChattingResponse.ChattingCreateResponseDTO toChattingCreateResponseDTO( + Chatting chatting) { return ChattingResponse.ChattingCreateResponseDTO.builder() .id(chatting.getId()) .createdAt(chatting.getCreatedAt()) .build(); } - public static List getMessagesExcludeLast(List messages){ + public static List getMessagesExcludeLast( + List messages) { return messages.subList(0, messages.size() - 1); } - public static Optional getLastMessage(List messages){ - if(messages.isEmpty()) - return Optional.empty(); + public static Optional getLastMessage( + List messages) { + if (messages.isEmpty()) return Optional.empty(); final int lastIndex = messages.size() - 1; return Optional.of(messages.get(lastIndex)); } diff --git a/src/main/java/briefing/chatting/application/ChatGptClient.java b/src/main/java/briefing/chatting/application/ChatGptClient.java index 9a107cb..7e45895 100644 --- a/src/main/java/briefing/chatting/application/ChatGptClient.java +++ b/src/main/java/briefing/chatting/application/ChatGptClient.java @@ -1,13 +1,7 @@ package briefing.chatting.application; -import briefing.chatting.application.ChatGptClient.GptAnswerResponse.Choice.ChoiceMessage; -import briefing.chatting.application.dto.ChattingRequest; -import briefing.chatting.domain.Chatting; -import briefing.chatting.domain.GptModel; -import briefing.chatting.domain.Message; -import briefing.chatting.domain.MessageRole; import java.util.List; -import lombok.RequiredArgsConstructor; + import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -16,76 +10,77 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import briefing.chatting.application.ChatGptClient.GptAnswerResponse.Choice.ChoiceMessage; +import briefing.chatting.application.dto.ChattingRequest; +import briefing.chatting.domain.Chatting; +import briefing.chatting.domain.GptModel; +import briefing.chatting.domain.Message; +import briefing.chatting.domain.MessageRole; +import lombok.RequiredArgsConstructor; + @Component @RequiredArgsConstructor public class ChatGptClient { - private static final float TEMPERATURE = 1; + private static final float TEMPERATURE = 1; - private final RestTemplate restTemplate; + private final RestTemplate restTemplate; - @Value("${openai.url.chat}") - private String chatUrl; - @Value("${openai.token}") - private String token; + @Value("${openai.url.chat}") + private String chatUrl; - public Message requestAnswer(final Chatting chatting, final ChattingRequest.AnswerRequestDTO request) { - final HttpEntity requestEntity = generateRequestEntity(request); + @Value("${openai.token}") + private String token; - final GptAnswerResponse response = restTemplate.exchange(chatUrl, HttpMethod.POST, - requestEntity, GptAnswerResponse.class) - .getBody(); + public Message requestAnswer( + final Chatting chatting, final ChattingRequest.AnswerRequestDTO request) { + final HttpEntity requestEntity = generateRequestEntity(request); - return new Message(chatting, response.getRole(), response.getContent()); - } + final GptAnswerResponse response = + restTemplate + .exchange(chatUrl, HttpMethod.POST, requestEntity, GptAnswerResponse.class) + .getBody(); - private HttpEntity generateRequestEntity(final ChattingRequest.AnswerRequestDTO request) { - final HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setBearerAuth(token); - - final GptAnswerRequest requestBody = GptAnswerRequest.from(request, TEMPERATURE); + return new Message(chatting, response.getRole(), response.getContent()); + } - return new HttpEntity<>(requestBody, headers); - } + private HttpEntity generateRequestEntity( + final ChattingRequest.AnswerRequestDTO request) { + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); - record GptAnswerRequest(GptModel model, float temperature, List messages) { + final GptAnswerRequest requestBody = GptAnswerRequest.from(request, TEMPERATURE); - public static GptAnswerRequest from(final ChattingRequest.AnswerRequestDTO request, final float temperature) { - return new GptAnswerRequest( - request.getModel(), - temperature, - request.getMessages() - ); + return new HttpEntity<>(requestBody, headers); } - } - record GptAnswerResponse( - List choices - ) { + record GptAnswerRequest( + GptModel model, float temperature, List messages) { - public MessageRole getRole() { - return getAnswer().role(); + public static GptAnswerRequest from( + final ChattingRequest.AnswerRequestDTO request, final float temperature) { + return new GptAnswerRequest(request.getModel(), temperature, request.getMessages()); + } } - public String getContent() { - return getAnswer().content(); - } + record GptAnswerResponse(List choices) { - private ChoiceMessage getAnswer() { - return choices.get(0).message(); - } + public MessageRole getRole() { + return getAnswer().role(); + } + + public String getContent() { + return getAnswer().content(); + } - record Choice( - ChoiceMessage message - ) { + private ChoiceMessage getAnswer() { + return choices.get(0).message(); + } - record ChoiceMessage( - MessageRole role, - String content - ) { + record Choice(ChoiceMessage message) { - } + record ChoiceMessage(MessageRole role, String content) {} + } } - } } diff --git a/src/main/java/briefing/chatting/application/ChattingCommandService.java b/src/main/java/briefing/chatting/application/ChattingCommandService.java index 73f7f31..452702a 100644 --- a/src/main/java/briefing/chatting/application/ChattingCommandService.java +++ b/src/main/java/briefing/chatting/application/ChattingCommandService.java @@ -1,84 +1,92 @@ package briefing.chatting.application; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.chatting.api.ChattingConverter; import briefing.chatting.application.dto.*; import briefing.chatting.domain.Chatting; import briefing.chatting.domain.Message; import briefing.chatting.domain.repository.ChattingRepository; import briefing.chatting.domain.repository.MessageRepository; -import java.util.List; - import briefing.exception.ErrorCode; import briefing.exception.handler.ChattingException; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @Transactional @RequiredArgsConstructor public class ChattingCommandService { - private final ChattingRepository chattingRepository; - private final MessageRepository messageRepository; - private final ChatGptClient chatGptClient; + private final ChattingRepository chattingRepository; + private final MessageRepository messageRepository; + private final ChatGptClient chatGptClient; + + public Chatting createChatting() { + final Chatting chatting = chattingRepository.save(new Chatting()); + return chatting; + } - public Chatting createChatting() { - final Chatting chatting = chattingRepository.save(new Chatting()); - return chatting; - } + public Message requestAnswer(final Long id, final ChattingRequest.AnswerRequestDTO request) { + final Chatting chatting = + chattingRepository + .findById(id) + .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_CHATTING)); - public Message requestAnswer(final Long id, final ChattingRequest.AnswerRequestDTO request) { - final Chatting chatting = chattingRepository.findById(id) - .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_CHATTING)); + final ChattingRequest.MessageRequestDTO lastMessage = + ChattingConverter.getLastMessage(request.getMessages()) + .orElseThrow(() -> new ChattingException(ErrorCode.LAST_MESSAGE_NOT_EXIST)); + validateLastMessage(lastMessage); - final ChattingRequest.MessageRequestDTO lastMessage = ChattingConverter.getLastMessage(request.getMessages()) - .orElseThrow(() -> new ChattingException(ErrorCode.LAST_MESSAGE_NOT_EXIST)); - validateLastMessage(lastMessage); + final Message question = + Message.builder() + .chatting(chatting) + .role(lastMessage.getRole()) + .content(lastMessage.getContent()) + .build(); + final Message answer = chatGptClient.requestAnswer(chatting, request); - final Message question = Message.builder() - .chatting(chatting) - .role(lastMessage.getRole()) - .content(lastMessage.getContent()) - .build(); - final Message answer = chatGptClient.requestAnswer(chatting, request); + if (chatting.isNotInitialized()) { + initChatting(request, chatting, question); + } + messageRepository.save(question); + messageRepository.save(answer); - if (chatting.isNotInitialized()) { - initChatting(request, chatting, question); + return answer; } - messageRepository.save(question); - messageRepository.save(answer); - return answer; - } + private void initChatting( + final ChattingRequest.AnswerRequestDTO request, + final Chatting chatting, + final Message question) { + chatting.updateTitle(question.getContent()); - private void initChatting(final ChattingRequest.AnswerRequestDTO request, final Chatting chatting, - final Message question) { - chatting.updateTitle(question.getContent()); + final List messages = + ChattingConverter.getMessagesExcludeLast(request.getMessages()).stream() + .map( + message -> + Message.builder() + .chatting(chatting) + .role(message.getRole()) + .content(message.getContent()) + .build()) + .toList(); - final List messages = ChattingConverter.getMessagesExcludeLast(request.getMessages()).stream() - .map( - message -> Message.builder() - .chatting(chatting) - .role(message.getRole()) - .content(message.getContent()) - .build() - ) - .toList(); - - messageRepository.saveAll(messages); - } - - private void validateLastMessage(final ChattingRequest.MessageRequestDTO questionRequest) { - if (questionRequest.getRole().isNotUser()) { - throw new ChattingException(ErrorCode.BAD_LAST_MESSAGE_ROLE); + messageRepository.saveAll(messages); } - if (isInvalidContent(questionRequest)) { - throw new ChattingException(ErrorCode.CAN_NOT_EMPTY_CONTENT); + + private void validateLastMessage(final ChattingRequest.MessageRequestDTO questionRequest) { + if (questionRequest.getRole().isNotUser()) { + throw new ChattingException(ErrorCode.BAD_LAST_MESSAGE_ROLE); + } + if (isInvalidContent(questionRequest)) { + throw new ChattingException(ErrorCode.CAN_NOT_EMPTY_CONTENT); + } } - } - private boolean isInvalidContent(final ChattingRequest.MessageRequestDTO message) { - return message.getContent() == null || message.getContent().isBlank(); - } + private boolean isInvalidContent(final ChattingRequest.MessageRequestDTO message) { + return message.getContent() == null || message.getContent().isBlank(); + } } diff --git a/src/main/java/briefing/chatting/application/ChattingQueryService.java b/src/main/java/briefing/chatting/application/ChattingQueryService.java index 0439d75..0ea741f 100644 --- a/src/main/java/briefing/chatting/application/ChattingQueryService.java +++ b/src/main/java/briefing/chatting/application/ChattingQueryService.java @@ -1,33 +1,35 @@ package briefing.chatting.application; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import briefing.chatting.domain.Chatting; import briefing.chatting.domain.repository.ChattingRepository; -import java.util.List; - import briefing.exception.ErrorCode; import briefing.exception.handler.ChattingException; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class ChattingQueryService { - private final ChattingRepository chattingRepository; + private final ChattingRepository chattingRepository; - public List findChattings(final List ids) { - final List chattings = chattingRepository.findAllById(ids); + public List findChattings(final List ids) { + final List chattings = chattingRepository.findAllById(ids); - return chattings; - } + return chattings; + } - public Chatting findChatting(final Long id) { - final Chatting chatting = chattingRepository.findById(id) - .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_CHATTING)); + public Chatting findChatting(final Long id) { + final Chatting chatting = + chattingRepository + .findById(id) + .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_CHATTING)); - return chatting; - } + return chatting; + } } diff --git a/src/main/java/briefing/chatting/application/dto/ChattingRequest.java b/src/main/java/briefing/chatting/application/dto/ChattingRequest.java index 3e51926..4fe7b2d 100644 --- a/src/main/java/briefing/chatting/application/dto/ChattingRequest.java +++ b/src/main/java/briefing/chatting/application/dto/ChattingRequest.java @@ -1,23 +1,22 @@ package briefing.chatting.application.dto; +import java.util.List; + import briefing.chatting.domain.GptModel; import briefing.chatting.domain.MessageRole; import lombok.Getter; -import java.util.List; -import java.util.Optional; - public class ChattingRequest { @Getter - public static class MessageRequestDTO{ + public static class MessageRequestDTO { MessageRole role; String content; } @Getter - public static class AnswerRequestDTO{ + public static class AnswerRequestDTO { GptModel model; List messages; } -} \ No newline at end of file +} diff --git a/src/main/java/briefing/chatting/application/dto/ChattingResponse.java b/src/main/java/briefing/chatting/application/dto/ChattingResponse.java index 79260f5..4aaf743 100644 --- a/src/main/java/briefing/chatting/application/dto/ChattingResponse.java +++ b/src/main/java/briefing/chatting/application/dto/ChattingResponse.java @@ -1,21 +1,21 @@ package briefing.chatting.application.dto; +import java.time.LocalDateTime; +import java.util.List; + import briefing.chatting.domain.MessageRole; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; -import java.util.List; - public class ChattingResponse { @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class AnswerResponseDTO{ + public static class AnswerResponseDTO { Long id; MessageRole role; String content; @@ -26,7 +26,7 @@ public static class AnswerResponseDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class MessageResponseDTO{ + public static class MessageResponseDTO { Long id; MessageRole role; String content; @@ -37,7 +37,7 @@ public static class MessageResponseDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class ChattingDetailResponseDTO{ + public static class ChattingDetailResponseDTO { Long id; String title; List messages; @@ -47,7 +47,7 @@ public static class ChattingDetailResponseDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class ChattingResponseDTO{ + public static class ChattingResponseDTO { Long id; String title; LocalDateTime createdAt; @@ -57,7 +57,7 @@ public static class ChattingResponseDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class ChattingListResponseDTO{ + public static class ChattingListResponseDTO { List chattings; } @@ -65,7 +65,7 @@ public static class ChattingListResponseDTO{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class ChattingCreateResponseDTO{ + public static class ChattingCreateResponseDTO { Long id; LocalDateTime createdAt; } diff --git a/src/main/java/briefing/chatting/domain/Chatting.java b/src/main/java/briefing/chatting/domain/Chatting.java index c0445b0..ed886dd 100644 --- a/src/main/java/briefing/chatting/domain/Chatting.java +++ b/src/main/java/briefing/chatting/domain/Chatting.java @@ -1,44 +1,47 @@ package briefing.chatting.domain; -import briefing.base.BaseDateTimeEntity; +import java.util.List; + import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import java.util.List; + +import briefing.base.BaseDateTimeEntity; import lombok.Getter; @Entity @Getter public class Chatting extends BaseDateTimeEntity { - private static final int LIMIT_TITLE_LENGTH = 20; + private static final int LIMIT_TITLE_LENGTH = 20; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - private String title; - @OneToMany(mappedBy = "chatting", fetch = FetchType.LAZY) - private List messages; + private String title; - public Chatting() { - } + @OneToMany(mappedBy = "chatting", fetch = FetchType.LAZY) + private List messages; - public boolean isNotInitialized() { - return title == null || title.isBlank(); - } + public Chatting() {} - public void updateTitle(final String content) { - this.title = convertToTitle(content); - } + public boolean isNotInitialized() { + return title == null || title.isBlank(); + } + + public void updateTitle(final String content) { + this.title = convertToTitle(content); + } - private String convertToTitle(final String content) { - if (content.length() > LIMIT_TITLE_LENGTH) { - final String substring = content.substring(0, LIMIT_TITLE_LENGTH - 3); - return substring + "..."; + private String convertToTitle(final String content) { + if (content.length() > LIMIT_TITLE_LENGTH) { + final String substring = content.substring(0, LIMIT_TITLE_LENGTH - 3); + return substring + "..."; + } + return content; } - return content; - } } diff --git a/src/main/java/briefing/chatting/domain/GptModel.java b/src/main/java/briefing/chatting/domain/GptModel.java index 1b34787..56c8e12 100644 --- a/src/main/java/briefing/chatting/domain/GptModel.java +++ b/src/main/java/briefing/chatting/domain/GptModel.java @@ -1,27 +1,28 @@ package briefing.chatting.domain; -import briefing.exception.ErrorCode; -import briefing.exception.handler.ChattingException; +import java.util.Arrays; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Arrays; + +import briefing.exception.ErrorCode; +import briefing.exception.handler.ChattingException; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public enum GptModel { - GPT_3_5_TURBO("gpt-3.5-turbo"), - GPT_4("gpt-4"); + GPT_3_5_TURBO("gpt-3.5-turbo"), + GPT_4("gpt-4"); - @JsonValue - private final String value; + @JsonValue private final String value; - @JsonCreator - public static GptModel from(final String model) { - return Arrays.stream(values()) - .filter(value -> value.getValue().equals(model)) - .findAny() - .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_MODEL)); - } + @JsonCreator + public static GptModel from(final String model) { + return Arrays.stream(values()) + .filter(value -> value.getValue().equals(model)) + .findAny() + .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_MODEL)); + } } diff --git a/src/main/java/briefing/chatting/domain/Message.java b/src/main/java/briefing/chatting/domain/Message.java index b6341ec..599da4b 100644 --- a/src/main/java/briefing/chatting/domain/Message.java +++ b/src/main/java/briefing/chatting/domain/Message.java @@ -1,5 +1,7 @@ package briefing.chatting.domain; +import java.time.LocalDateTime; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -11,12 +13,12 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import java.time.LocalDateTime; -import lombok.*; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import lombok.*; + @Entity @EntityListeners(AuditingEntityListener.class) @Getter @@ -25,23 +27,27 @@ @AllArgsConstructor public class Message { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(nullable = false) - private Chatting chatting; - @Enumerated(EnumType.STRING) - private MessageRole role; - @Column(nullable = false, length = 1024) - private String content; - @CreatedDate - @Column(updatable = false) - private LocalDateTime createdAt; - - public Message(final Chatting chatting, final MessageRole role, final String content) { - this.chatting = chatting; - this.role = role; - this.content = content; - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false) + private Chatting chatting; + + @Enumerated(EnumType.STRING) + private MessageRole role; + + @Column(nullable = false, length = 1024) + private String content; + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + + public Message(final Chatting chatting, final MessageRole role, final String content) { + this.chatting = chatting; + this.role = role; + this.content = content; + } } diff --git a/src/main/java/briefing/chatting/domain/MessageRole.java b/src/main/java/briefing/chatting/domain/MessageRole.java index 86f329e..34e79ce 100644 --- a/src/main/java/briefing/chatting/domain/MessageRole.java +++ b/src/main/java/briefing/chatting/domain/MessageRole.java @@ -1,32 +1,33 @@ package briefing.chatting.domain; -import briefing.exception.ErrorCode; -import briefing.exception.handler.ChattingException; +import java.util.Arrays; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Arrays; + +import briefing.exception.ErrorCode; +import briefing.exception.handler.ChattingException; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public enum MessageRole { - SYSTEM("system"), - ASSISTANT("assistant"), - USER("user"); + SYSTEM("system"), + ASSISTANT("assistant"), + USER("user"); - @JsonValue - private final String value; + @JsonValue private final String value; - @JsonCreator - public static MessageRole from(final String role) { - return Arrays.stream(values()) - .filter(value -> value.getValue().equals(role)) - .findAny() - .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_ROLE)); - } + @JsonCreator + public static MessageRole from(final String role) { + return Arrays.stream(values()) + .filter(value -> value.getValue().equals(role)) + .findAny() + .orElseThrow(() -> new ChattingException(ErrorCode.NOT_FOUND_ROLE)); + } - public boolean isNotUser() { - return !this.equals(USER); - } + public boolean isNotUser() { + return !this.equals(USER); + } } diff --git a/src/main/java/briefing/chatting/domain/repository/ChattingRepository.java b/src/main/java/briefing/chatting/domain/repository/ChattingRepository.java index b3544d8..cfa85d2 100644 --- a/src/main/java/briefing/chatting/domain/repository/ChattingRepository.java +++ b/src/main/java/briefing/chatting/domain/repository/ChattingRepository.java @@ -1,10 +1,9 @@ package briefing.chatting.domain.repository; -import briefing.chatting.domain.Chatting; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -@Repository -public interface ChattingRepository extends JpaRepository { +import briefing.chatting.domain.Chatting; -} +@Repository +public interface ChattingRepository extends JpaRepository {} diff --git a/src/main/java/briefing/chatting/domain/repository/MessageRepository.java b/src/main/java/briefing/chatting/domain/repository/MessageRepository.java index cd1cb19..6f82ee0 100644 --- a/src/main/java/briefing/chatting/domain/repository/MessageRepository.java +++ b/src/main/java/briefing/chatting/domain/repository/MessageRepository.java @@ -1,10 +1,9 @@ package briefing.chatting.domain.repository; -import briefing.chatting.domain.Message; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -@Repository -public interface MessageRepository extends JpaRepository { +import briefing.chatting.domain.Message; -} +@Repository +public interface MessageRepository extends JpaRepository {} diff --git a/src/main/java/briefing/common/enums/APIVersion.java b/src/main/java/briefing/common/enums/APIVersion.java index 4908e64..e6ff06d 100644 --- a/src/main/java/briefing/common/enums/APIVersion.java +++ b/src/main/java/briefing/common/enums/APIVersion.java @@ -1,13 +1,14 @@ package briefing.common.enums; +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonValue; + import briefing.exception.ErrorCode; import briefing.exception.handler.BriefingException; -import com.fasterxml.jackson.annotation.JsonValue; import lombok.AllArgsConstructor; import lombok.Getter; -import java.util.Arrays; - @Getter @AllArgsConstructor public enum APIVersion { diff --git a/src/main/java/briefing/common/response/CommonResponse.java b/src/main/java/briefing/common/response/CommonResponse.java index 813d1d9..43b728b 100644 --- a/src/main/java/briefing/common/response/CommonResponse.java +++ b/src/main/java/briefing/common/response/CommonResponse.java @@ -1,8 +1,8 @@ package briefing.common.response; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; + import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,23 +13,18 @@ public class CommonResponse { @JsonProperty("isSuccess") private final Boolean isSuccess; + private final String code; private final String message; private T result; - - // ์š”์ฒญ์— ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ11 - - - public static CommonResponse onSuccess(T data) { - return new CommonResponse<>(true, "์š”์ฒญ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.","1000", data); + return new CommonResponse<>(true, "์š”์ฒญ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.", "1000", data); } public static CommonResponse onFailure(String code, String message, T data) { return new CommonResponse<>(false, code, message, data); } - } diff --git a/src/main/java/briefing/config/GlobalWebConfig.java b/src/main/java/briefing/config/GlobalWebConfig.java index 1623242..50466fe 100644 --- a/src/main/java/briefing/config/GlobalWebConfig.java +++ b/src/main/java/briefing/config/GlobalWebConfig.java @@ -1,43 +1,45 @@ package briefing.config; -import briefing.converter.*; -import briefing.security.handler.annotation.AuthUserArgumentResolver; -import lombok.RequiredArgsConstructor; +import java.util.List; + import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; +import briefing.converter.*; +import briefing.security.handler.annotation.AuthUserArgumentResolver; +import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor public class GlobalWebConfig implements WebMvcConfigurer { - private final AuthUserArgumentResolver authUserArgumentResolver; - @Override - public void addArgumentResolvers(List resolverList) { - resolverList.add(authUserArgumentResolver); - } - @Override - public void addFormatters(final FormatterRegistry registry) { - registry.addConverter(new BriefingTypeRequestConverter()); - registry.addConverter(new GptModelRequestConverter()); - registry.addConverter(new MessageRoleRequestConverter()); - registry.addConverter(new SocialTypeRequestConverter()); - registry.addConverter(new TimeOfDayConverter()); - registry.addConverter(new APIVersionRequestConverter()); - } - - @Override - public void addCorsMappings(final CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("*") - .allowedMethods("*") - .allowedHeaders("*") - .allowCredentials(false) - .maxAge(6000); - - } + private final AuthUserArgumentResolver authUserArgumentResolver; + + @Override + public void addArgumentResolvers(List resolverList) { + resolverList.add(authUserArgumentResolver); + } + + @Override + public void addFormatters(final FormatterRegistry registry) { + registry.addConverter(new BriefingTypeRequestConverter()); + registry.addConverter(new GptModelRequestConverter()); + registry.addConverter(new MessageRoleRequestConverter()); + registry.addConverter(new SocialTypeRequestConverter()); + registry.addConverter(new TimeOfDayConverter()); + registry.addConverter(new APIVersionRequestConverter()); + } + + @Override + public void addCorsMappings(final CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("*") + .allowedHeaders("*") + .allowCredentials(false) + .maxAge(6000); + } } diff --git a/src/main/java/briefing/config/JpaAuditingConfig.java b/src/main/java/briefing/config/JpaAuditingConfig.java index faf69c8..3bacc72 100644 --- a/src/main/java/briefing/config/JpaAuditingConfig.java +++ b/src/main/java/briefing/config/JpaAuditingConfig.java @@ -5,6 +5,4 @@ @Configuration @EnableJpaAuditing -public class JpaAuditingConfig { - -} +public class JpaAuditingConfig {} diff --git a/src/main/java/briefing/config/QueryDslConfig.java b/src/main/java/briefing/config/QueryDslConfig.java index fded353..4d4db3b 100644 --- a/src/main/java/briefing/config/QueryDslConfig.java +++ b/src/main/java/briefing/config/QueryDslConfig.java @@ -1,20 +1,22 @@ package briefing.config; -import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; -import lombok.RequiredArgsConstructor; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + @Configuration @RequiredArgsConstructor public class QueryDslConfig { - private final EntityManager em; @Bean - public JPAQueryFactory jpaQueryFactory(){ + public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(em); } -} \ No newline at end of file +} diff --git a/src/main/java/briefing/config/RestTemplateConfig.java b/src/main/java/briefing/config/RestTemplateConfig.java index 704b144..c99f07b 100644 --- a/src/main/java/briefing/config/RestTemplateConfig.java +++ b/src/main/java/briefing/config/RestTemplateConfig.java @@ -7,8 +7,8 @@ @Configuration public class RestTemplateConfig { - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/briefing/config/SwaggerConfig.java b/src/main/java/briefing/config/SwaggerConfig.java index ee9b04d..68369d1 100644 --- a/src/main/java/briefing/config/SwaggerConfig.java +++ b/src/main/java/briefing/config/SwaggerConfig.java @@ -1,43 +1,42 @@ package briefing.config; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; -import org.springdoc.core.models.GroupedOpenApi; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; @Configuration public class SwaggerConfig { @Bean public GroupedOpenApi publicApi() { - return GroupedOpenApi.builder() - .group("v1-definition") - .packagesToScan("briefing") - .build(); + return GroupedOpenApi.builder().group("v1-definition").packagesToScan("briefing").build(); } @Bean public OpenAPI breifingAPI() { - Info info = new Info() - .title("Breifing API") - .description("Breifing API ๋ช…์„ธ์„œ") - .version("1.0.0"); + Info info = + new Info().title("Breifing API").description("Breifing API ๋ช…์„ธ์„œ").version("1.0.0"); String jwtSchemeName = "JWT TOKEN"; // API ์š”์ฒญํ—ค๋”์— ์ธ์ฆ์ •๋ณด ํฌํ•จ SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); // SecuritySchemes ๋“ฑ๋ก - Components components = new Components() - .addSecuritySchemes(jwtSchemeName, new SecurityScheme() - .name(jwtSchemeName) - .type(SecurityScheme.Type.HTTP) // HTTP ๋ฐฉ์‹ - .scheme("bearer") - .bearerFormat("JWT")); + Components components = + new Components() + .addSecuritySchemes( + jwtSchemeName, + new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) // HTTP ๋ฐฉ์‹ + .scheme("bearer") + .bearerFormat("JWT")); return new OpenAPI() .addServersItem(new Server().url("/")) diff --git a/src/main/java/briefing/converter/APIVersionRequestConverter.java b/src/main/java/briefing/converter/APIVersionRequestConverter.java index fab93fc..31843d7 100644 --- a/src/main/java/briefing/converter/APIVersionRequestConverter.java +++ b/src/main/java/briefing/converter/APIVersionRequestConverter.java @@ -1,9 +1,11 @@ package briefing.converter; +import org.springframework.core.convert.converter.Converter; + import briefing.common.enums.APIVersion; import lombok.NonNull; -import org.springframework.core.convert.converter.Converter; -public class APIVersionRequestConverter implements Converter{ + +public class APIVersionRequestConverter implements Converter { @Override public APIVersion convert(@NonNull final String source) { diff --git a/src/main/java/briefing/converter/BriefingTypeRequestConverter.java b/src/main/java/briefing/converter/BriefingTypeRequestConverter.java index 3a8f547..609be25 100644 --- a/src/main/java/briefing/converter/BriefingTypeRequestConverter.java +++ b/src/main/java/briefing/converter/BriefingTypeRequestConverter.java @@ -1,13 +1,14 @@ package briefing.converter; +import org.springframework.core.convert.converter.Converter; + import briefing.briefing.domain.BriefingType; import lombok.NonNull; -import org.springframework.core.convert.converter.Converter; public class BriefingTypeRequestConverter implements Converter { - @Override - public BriefingType convert(@NonNull final String type) { - return BriefingType.from(type); - } + @Override + public BriefingType convert(@NonNull final String type) { + return BriefingType.from(type); + } } diff --git a/src/main/java/briefing/converter/GptModelRequestConverter.java b/src/main/java/briefing/converter/GptModelRequestConverter.java index 79aff85..32fd44b 100644 --- a/src/main/java/briefing/converter/GptModelRequestConverter.java +++ b/src/main/java/briefing/converter/GptModelRequestConverter.java @@ -1,13 +1,14 @@ package briefing.converter; +import org.springframework.core.convert.converter.Converter; + import briefing.chatting.domain.GptModel; import lombok.NonNull; -import org.springframework.core.convert.converter.Converter; public class GptModelRequestConverter implements Converter { - @Override - public GptModel convert(@NonNull final String model) { - return GptModel.from(model); - } + @Override + public GptModel convert(@NonNull final String model) { + return GptModel.from(model); + } } diff --git a/src/main/java/briefing/converter/MessageRoleRequestConverter.java b/src/main/java/briefing/converter/MessageRoleRequestConverter.java index 472caba..55482fc 100644 --- a/src/main/java/briefing/converter/MessageRoleRequestConverter.java +++ b/src/main/java/briefing/converter/MessageRoleRequestConverter.java @@ -1,13 +1,14 @@ package briefing.converter; +import org.springframework.core.convert.converter.Converter; + import briefing.chatting.domain.MessageRole; import lombok.NonNull; -import org.springframework.core.convert.converter.Converter; public class MessageRoleRequestConverter implements Converter { - @Override - public MessageRole convert(@NonNull final String role) { - return MessageRole.from(role); - } + @Override + public MessageRole convert(@NonNull final String role) { + return MessageRole.from(role); + } } diff --git a/src/main/java/briefing/converter/SocialTypeRequestConverter.java b/src/main/java/briefing/converter/SocialTypeRequestConverter.java index 04f82a8..4b42824 100644 --- a/src/main/java/briefing/converter/SocialTypeRequestConverter.java +++ b/src/main/java/briefing/converter/SocialTypeRequestConverter.java @@ -1,12 +1,12 @@ package briefing.converter; -import briefing.member.domain.SocialType; import org.springframework.core.convert.converter.Converter; +import briefing.member.domain.SocialType; + public class SocialTypeRequestConverter implements Converter { @Override public SocialType convert(String source) { return SocialType.findByValue(source); } - } diff --git a/src/main/java/briefing/converter/TimeOfDayConverter.java b/src/main/java/briefing/converter/TimeOfDayConverter.java index fa0116e..123741d 100644 --- a/src/main/java/briefing/converter/TimeOfDayConverter.java +++ b/src/main/java/briefing/converter/TimeOfDayConverter.java @@ -1,8 +1,9 @@ package briefing.converter; +import org.springframework.core.convert.converter.Converter; + import briefing.briefing.domain.TimeOfDay; import lombok.NonNull; -import org.springframework.core.convert.converter.Converter; public class TimeOfDayConverter implements Converter { @Override diff --git a/src/main/java/briefing/exception/ErrorCode.java b/src/main/java/briefing/exception/ErrorCode.java index 81f97ef..22b763e 100644 --- a/src/main/java/briefing/exception/ErrorCode.java +++ b/src/main/java/briefing/exception/ErrorCode.java @@ -1,33 +1,33 @@ package briefing.exception; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; +import static org.springframework.http.HttpStatus.*; import java.util.Arrays; import java.util.Optional; import java.util.function.Predicate; -import static org.springframework.http.HttpStatus.*; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public enum ErrorCode { - _INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR, "COMMON000", "์„œ๋ฒ„ ์—๋Ÿฌ, ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ ๋ฐ”๋ž๋‹ˆ๋‹ค."), - _BAD_REQUEST(BAD_REQUEST,"COMMON001","์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค."), - _UNAUTHORIZED(UNAUTHORIZED,"COMMON002","๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."), + _BAD_REQUEST(BAD_REQUEST, "COMMON001", "์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค."), + _UNAUTHORIZED(UNAUTHORIZED, "COMMON002", "๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."), _METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED, "COMMON003", "์ง€์›ํ•˜์ง€ ์•Š๋Š” Http Method ์ž…๋‹ˆ๋‹ค."), _FORBIDDEN(FORBIDDEN, "COMMON004", "๊ธˆ์ง€๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค."), // ์ธ์ฆ ๊ด€๋ จ ์—๋Ÿฌ - ForbiddenException(UNAUTHORIZED,"AUTH002", "ํ•ด๋‹น ์š”์ฒญ์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค."), - UNAUTHORIZED_EXCEPTION (UNAUTHORIZED,"AUTH003", "๋กœ๊ทธ์ธ ํ›„ ์ด์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ† ํฐ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”"), - EXPIRED_JWT_EXCEPTION(UNAUTHORIZED,"AUTH004", "๊ธฐ์กด ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ํ•ด์ฃผ์„ธ์š”."), - RELOGIN_EXCEPTION(UNAUTHORIZED,"AUTH005", "๋ชจ๋“  ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”."), - INVALID_TOKEN_EXCEPTION(UNAUTHORIZED,"AUTH006","ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค." ), - HIJACK_JWT_TOKEN_EXCEPTION(UNAUTHORIZED,"AUTH007","ํƒˆ์ทจ๋œ(๋กœ๊ทธ์•„์›ƒ ๋œ) ํ† ํฐ์ž…๋‹ˆ๋‹ค ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”."), - INVALID_REFRESH_TOKEN(BAD_REQUEST,"AUTH009","๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”"), + ForbiddenException(UNAUTHORIZED, "AUTH002", "ํ•ด๋‹น ์š”์ฒญ์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค."), + UNAUTHORIZED_EXCEPTION(UNAUTHORIZED, "AUTH003", "๋กœ๊ทธ์ธ ํ›„ ์ด์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ† ํฐ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”"), + EXPIRED_JWT_EXCEPTION(UNAUTHORIZED, "AUTH004", "๊ธฐ์กด ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ํ•ด์ฃผ์„ธ์š”."), + RELOGIN_EXCEPTION(UNAUTHORIZED, "AUTH005", "๋ชจ๋“  ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”."), + INVALID_TOKEN_EXCEPTION(UNAUTHORIZED, "AUTH006", "ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), + HIJACK_JWT_TOKEN_EXCEPTION(UNAUTHORIZED, "AUTH007", "ํƒˆ์ทจ๋œ(๋กœ๊ทธ์•„์›ƒ ๋œ) ํ† ํฐ์ž…๋‹ˆ๋‹ค ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”."), + INVALID_REFRESH_TOKEN(BAD_REQUEST, "AUTH009", "๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”"), // oauth ์—๋Ÿฌ APPLE_BAD_REQUEST(BAD_REQUEST, "OAUTH001", "์• ํ”Œ ํ† ํฐ์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), @@ -41,9 +41,8 @@ public enum ErrorCode { FEIGN_EXPIRED_TOKEN(BAD_REQUEST, "FEIGN_400_4", "Feign server expired token"), FEIGN_NOT_FOUND(BAD_REQUEST, "FEIGN_400_5", "Feign server not found error"), FEIGN_INTERNAL_SERVER_ERROR(BAD_REQUEST, "FEIGN_400_6", "Feign server internal server error"), - FEIGN_METHOD_NOT_ALLOWED(BAD_REQUEST,"FEIGN_400_7" , "Feign server method not allowed"), - FEIGN_SERVER_ERROR(BAD_REQUEST,"FEIGN_500" , "Feign server error"), - + FEIGN_METHOD_NOT_ALLOWED(BAD_REQUEST, "FEIGN_400_7", "Feign server method not allowed"), + FEIGN_SERVER_ERROR(BAD_REQUEST, "FEIGN_500", "Feign server error"), // member ๊ด€๋ จ ์—๋Ÿฌ @@ -52,25 +51,23 @@ public enum ErrorCode { // member ์—๋Ÿฌ - // scrap ์—๋Ÿฌ SCRAP_ALREADY_EXISTS(CONFLICT, "SCRAP_001", "์ด๋ฏธ ์Šคํฌ๋žฉํ–ˆ์Šต๋‹ˆ๋‹ค."), SCRAP_NOT_FOUND(NOT_FOUND, "SCRAP_002", "์กด์žฌํ•˜์ง€ ์•Š๋Š” ์Šคํฌ๋žฉ์ž…๋‹ˆ๋‹ค."), DUPLICATE_SCRAP(CONFLICT, "SCRAP_003", "์ค‘๋ณต๋œ ์Šคํฌ๋žฉ ์š”์ฒญ์ž…๋‹ˆ๋‹ค."), // briefing ์—๋Ÿฌ - NOT_FOUND_BRIEFING(NOT_FOUND,"BRIEFING_001", "๋ธŒ๋ฆฌํ•‘์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), - NOT_FOUND_TYPE(BAD_REQUEST,"BRIEFING_002", "ํ•ด๋‹นํ•˜๋Š” type์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + NOT_FOUND_BRIEFING(NOT_FOUND, "BRIEFING_001", "๋ธŒ๋ฆฌํ•‘์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), + NOT_FOUND_TYPE(BAD_REQUEST, "BRIEFING_002", "ํ•ด๋‹นํ•˜๋Š” type์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), // chatting ์—๋Ÿฌ - NOT_FOUND_CHATTING(NOT_FOUND, "CHATTING_001","ํ•ด๋‹นํ•˜๋Š” ์ฑ„ํŒ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), - LAST_MESSAGE_NOT_EXIST(BAD_REQUEST,"CHATTING_002", "๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), - BAD_LAST_MESSAGE_ROLE(BAD_REQUEST, "CHATTING_003","๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€์˜ ์—ญํ• ์ด user๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค."), - CAN_NOT_EMPTY_CONTENT(BAD_REQUEST,"CHATTING_004" ,"content๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."), - NOT_FOUND_ROLE(BAD_REQUEST, "CHATTING_005","ํ•ด๋‹นํ•˜๋Š” role์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), - NOT_FOUND_MODEL(BAD_REQUEST, "CHATTING_006","ํ•ด๋‹นํ•˜๋Š” mode์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); - + NOT_FOUND_CHATTING(NOT_FOUND, "CHATTING_001", "ํ•ด๋‹นํ•˜๋Š” ์ฑ„ํŒ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + LAST_MESSAGE_NOT_EXIST(BAD_REQUEST, "CHATTING_002", "๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + BAD_LAST_MESSAGE_ROLE(BAD_REQUEST, "CHATTING_003", "๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€์˜ ์—ญํ• ์ด user๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค."), + CAN_NOT_EMPTY_CONTENT(BAD_REQUEST, "CHATTING_004", "content๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."), + NOT_FOUND_ROLE(BAD_REQUEST, "CHATTING_005", "ํ•ด๋‹นํ•˜๋Š” role์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + NOT_FOUND_MODEL(BAD_REQUEST, "CHATTING_006", "ํ•ด๋‹นํ•˜๋Š” mode์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); private final HttpStatus httpStatus; private final String code; @@ -95,16 +92,16 @@ public static ErrorCode valueOf(HttpStatus httpStatus) { return Arrays.stream(values()) .filter(errorCode -> errorCode.getHttpStatus() == httpStatus) .findFirst() - .orElseGet(() -> { - if (httpStatus.is4xxClientError()) { - return ErrorCode._BAD_REQUEST; - } else - return ErrorCode._INTERNAL_SERVER_ERROR; - }); + .orElseGet( + () -> { + if (httpStatus.is4xxClientError()) { + return ErrorCode._BAD_REQUEST; + } else return ErrorCode._INTERNAL_SERVER_ERROR; + }); } -// @Override -// public String toString() { -// return String.format("%s (%d)", this.name(), this.getCode()); -// } + // @Override + // public String toString() { + // return String.format("%s (%d)", this.name(), this.getCode()); + // } } diff --git a/src/main/java/briefing/exception/ExceptionAdvice.java b/src/main/java/briefing/exception/ExceptionAdvice.java index 4808baf..5a79dbb 100644 --- a/src/main/java/briefing/exception/ExceptionAdvice.java +++ b/src/main/java/briefing/exception/ExceptionAdvice.java @@ -1,15 +1,19 @@ package briefing.exception; -import briefing.common.response.CommonResponse; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolationException; import jakarta.validation.constraints.NotNull; -import lombok.extern.slf4j.Slf4j; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.User; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -20,113 +24,122 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; +import briefing.common.response.CommonResponse; +import lombok.extern.slf4j.Slf4j; @Slf4j @RestControllerAdvice(annotations = {RestController.class}) public class ExceptionAdvice extends ResponseEntityExceptionHandler { - @org.springframework.web.bind.annotation.ExceptionHandler public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { - String errorMessage = e.getConstraintViolations().stream() - .map(constraintViolation -> constraintViolation.getMessage()) - .findFirst() - .orElseThrow(() -> new RuntimeException("ConstraintViolationException ์ถ”์ถœ ๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ")); - - return handleExceptionInternalConstraint(e, ErrorCode.valueOf(errorMessage), HttpHeaders.EMPTY,request); + String errorMessage = + e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow( + () -> + new RuntimeException( + "ConstraintViolationException ์ถ”์ถœ ๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ")); + + return handleExceptionInternalConstraint( + e, ErrorCode.valueOf(errorMessage), HttpHeaders.EMPTY, request); } @Override @NotNull public ResponseEntity handleMethodArgumentNotValid( - MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { Map errors = new LinkedHashMap<>(); ex.getBindingResult().getFieldErrors().stream() - .forEach(fieldError -> { - String fieldName = fieldError.getField(); - String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); - errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); - }); - - return handleExceptionInternalArgs(ex,HttpHeaders.EMPTY,ErrorCode.valueOf("_BAD_REQUEST"),request,errors); + .forEach( + fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = + Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge( + fieldName, + errorMessage, + (existingErrorMessage, newErrorMessage) -> + existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs( + ex, HttpHeaders.EMPTY, ErrorCode.valueOf("_BAD_REQUEST"), request, errors); } @org.springframework.web.bind.annotation.ExceptionHandler public ResponseEntity exception(Exception e, WebRequest request) { e.printStackTrace(); - return handleExceptionInternalFalse(e, ErrorCode._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorCode._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage()); + return handleExceptionInternalFalse( + e, + ErrorCode._INTERNAL_SERVER_ERROR, + HttpHeaders.EMPTY, + ErrorCode._INTERNAL_SERVER_ERROR.getHttpStatus(), + request, + e.getMessage()); } @ExceptionHandler(value = GeneralException.class) - public ResponseEntity onThrowException(GeneralException generalException, - @AuthenticationPrincipal User user, HttpServletRequest request) { + public ResponseEntity onThrowException( + GeneralException generalException, + @AuthenticationPrincipal User user, + HttpServletRequest request) { getExceptionStackTrace(generalException, user, request); ErrorCode errorCode = generalException.getErrorCode(); - return handleExceptionInternal(generalException,errorCode,null,request); + return handleExceptionInternal(generalException, errorCode, null, request); } - private ResponseEntity handleExceptionInternal(Exception e, ErrorCode errorCode, - HttpHeaders headers, HttpServletRequest request) { + private ResponseEntity handleExceptionInternal( + Exception e, ErrorCode errorCode, HttpHeaders headers, HttpServletRequest request) { - CommonResponse body = CommonResponse.onFailure(errorCode.getCode(),errorCode.getMessage(),null); -// e.printStackTrace(); + CommonResponse body = + CommonResponse.onFailure(errorCode.getCode(), errorCode.getMessage(), null); + // e.printStackTrace(); WebRequest webRequest = new ServletWebRequest(request); return super.handleExceptionInternal( - e, - body, - headers, - errorCode.getHttpStatus(), - webRequest - ); + e, body, headers, errorCode.getHttpStatus(), webRequest); } - private ResponseEntity handleExceptionInternalFalse(Exception e, ErrorCode errorCode, - HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) { - CommonResponse body = CommonResponse.onFailure(errorCode.getCode(),errorCode.getMessage(),errorPoint); - return super.handleExceptionInternal( - e, - body, - headers, - status, - request - ); + private ResponseEntity handleExceptionInternalFalse( + Exception e, + ErrorCode errorCode, + HttpHeaders headers, + HttpStatus status, + WebRequest request, + String errorPoint) { + CommonResponse body = + CommonResponse.onFailure(errorCode.getCode(), errorCode.getMessage(), errorPoint); + return super.handleExceptionInternal(e, body, headers, status, request); } - private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorCode errorCode, - WebRequest request, Map errorArgs) { - CommonResponse body = CommonResponse.onFailure(errorCode.getCode(),errorCode.getMessage(),errorArgs); - return super.handleExceptionInternal( - e, - body, - headers, - errorCode.getHttpStatus(), - request - ); + private ResponseEntity handleExceptionInternalArgs( + Exception e, + HttpHeaders headers, + ErrorCode errorCode, + WebRequest request, + Map errorArgs) { + CommonResponse body = + CommonResponse.onFailure(errorCode.getCode(), errorCode.getMessage(), errorArgs); + return super.handleExceptionInternal(e, body, headers, errorCode.getHttpStatus(), request); } - private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorCode errorCode, - HttpHeaders headers, WebRequest request) { - CommonResponse body = CommonResponse.onFailure(errorCode.getCode(), errorCode.getMessage(), null); - return super.handleExceptionInternal( - e, - body, - headers, - errorCode.getHttpStatus(), - request - ); + private ResponseEntity handleExceptionInternalConstraint( + Exception e, ErrorCode errorCode, HttpHeaders headers, WebRequest request) { + CommonResponse body = + CommonResponse.onFailure(errorCode.getCode(), errorCode.getMessage(), null); + return super.handleExceptionInternal(e, body, headers, errorCode.getHttpStatus(), request); } - private void getExceptionStackTrace(Exception e, @AuthenticationPrincipal User user, - HttpServletRequest request) { + private void getExceptionStackTrace( + Exception e, @AuthenticationPrincipal User user, HttpServletRequest request) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); diff --git a/src/main/java/briefing/exception/GeneralException.java b/src/main/java/briefing/exception/GeneralException.java index 651f01e..a6b58ee 100644 --- a/src/main/java/briefing/exception/GeneralException.java +++ b/src/main/java/briefing/exception/GeneralException.java @@ -3,7 +3,7 @@ import lombok.Getter; @Getter -public class GeneralException extends RuntimeException{ +public class GeneralException extends RuntimeException { private final ErrorCode errorCode; public GeneralException() { diff --git a/src/main/java/briefing/exception/common/ApiErrorResult.java b/src/main/java/briefing/exception/common/ApiErrorResult.java index f5fe79c..0274979 100644 --- a/src/main/java/briefing/exception/common/ApiErrorResult.java +++ b/src/main/java/briefing/exception/common/ApiErrorResult.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + import lombok.*; @Builder @@ -16,10 +17,10 @@ public class ApiErrorResult { private Object result; @Override - public String toString(){ - try{ + public String toString() { + try { return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); - }catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/briefing/exception/handler/AppleOAuthException.java b/src/main/java/briefing/exception/handler/AppleOAuthException.java index 32f9b81..3270b24 100644 --- a/src/main/java/briefing/exception/handler/AppleOAuthException.java +++ b/src/main/java/briefing/exception/handler/AppleOAuthException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class AppleOAuthException extends GeneralException { - public AppleOAuthException(ErrorCode errorCode){ + public AppleOAuthException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/exception/handler/BriefingException.java b/src/main/java/briefing/exception/handler/BriefingException.java index 56092b3..233c9e3 100644 --- a/src/main/java/briefing/exception/handler/BriefingException.java +++ b/src/main/java/briefing/exception/handler/BriefingException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class BriefingException extends GeneralException { - public BriefingException(ErrorCode errorCode){ + public BriefingException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/exception/handler/ChattingException.java b/src/main/java/briefing/exception/handler/ChattingException.java index 7f9412f..908618d 100644 --- a/src/main/java/briefing/exception/handler/ChattingException.java +++ b/src/main/java/briefing/exception/handler/ChattingException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class ChattingException extends GeneralException { - public ChattingException(ErrorCode errorCode){ + public ChattingException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/exception/handler/CustomFeignClientException.java b/src/main/java/briefing/exception/handler/CustomFeignClientException.java index fb1d507..b2f5a9e 100644 --- a/src/main/java/briefing/exception/handler/CustomFeignClientException.java +++ b/src/main/java/briefing/exception/handler/CustomFeignClientException.java @@ -5,7 +5,7 @@ public class CustomFeignClientException extends GeneralException { - public CustomFeignClientException(ErrorCode errorCode){ + public CustomFeignClientException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/exception/handler/JwtAuthenticationException.java b/src/main/java/briefing/exception/handler/JwtAuthenticationException.java index 39553ec..d3b21ef 100644 --- a/src/main/java/briefing/exception/handler/JwtAuthenticationException.java +++ b/src/main/java/briefing/exception/handler/JwtAuthenticationException.java @@ -1,11 +1,12 @@ package briefing.exception.handler; -import briefing.exception.ErrorCode; import org.springframework.security.core.AuthenticationException; +import briefing.exception.ErrorCode; + public class JwtAuthenticationException extends AuthenticationException { - public JwtAuthenticationException(ErrorCode code){ + public JwtAuthenticationException(ErrorCode code) { super(code.name()); } } diff --git a/src/main/java/briefing/exception/handler/MemberException.java b/src/main/java/briefing/exception/handler/MemberException.java index dc96a46..54c7f10 100644 --- a/src/main/java/briefing/exception/handler/MemberException.java +++ b/src/main/java/briefing/exception/handler/MemberException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class MemberException extends GeneralException { - public MemberException(ErrorCode errorCode){ + public MemberException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/exception/handler/RefreshTokenException.java b/src/main/java/briefing/exception/handler/RefreshTokenException.java index fe54331..ed23888 100644 --- a/src/main/java/briefing/exception/handler/RefreshTokenException.java +++ b/src/main/java/briefing/exception/handler/RefreshTokenException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class RefreshTokenException extends GeneralException { - public RefreshTokenException(ErrorCode errorCode){ + public RefreshTokenException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/feign/exception/FeignClientExceptionErrorDecoder.java b/src/main/java/briefing/feign/exception/FeignClientExceptionErrorDecoder.java index 245298d..84995f7 100644 --- a/src/main/java/briefing/feign/exception/FeignClientExceptionErrorDecoder.java +++ b/src/main/java/briefing/feign/exception/FeignClientExceptionErrorDecoder.java @@ -1,12 +1,13 @@ package briefing.feign.exception; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import briefing.exception.ErrorCode; import briefing.exception.handler.CustomFeignClientException; import feign.Response; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Slf4j public class FeignClientExceptionErrorDecoder implements ErrorDecoder { @@ -15,20 +16,20 @@ public class FeignClientExceptionErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { - if (response.status() >= 400 && response.status() <= 499){ + if (response.status() >= 400 && response.status() <= 499) { logger.error("feign 400๋ฒˆ๋Œ€ ์—๋Ÿฌ ๋ฐœ์ƒ : {}", response.reason()); - return switch (response.status()){ - case 401-> new CustomFeignClientException(ErrorCode.FEIGN_BAD_REQUEST); - case 402->new CustomFeignClientException(ErrorCode.FEIGN_UNAUTHORIZED); - case 403->new CustomFeignClientException(ErrorCode.FEIGN_FORBIDDEN); - case 404->new CustomFeignClientException(ErrorCode.FEIGN_EXPIRED_TOKEN); - case 405->new CustomFeignClientException(ErrorCode.FEIGN_NOT_FOUND); - case 406->new CustomFeignClientException(ErrorCode.FEIGN_INTERNAL_SERVER_ERROR); - case 407->new CustomFeignClientException(ErrorCode.FEIGN_METHOD_NOT_ALLOWED); - default -> throw new IllegalStateException("Unexpected value: " + response.status()); + return switch (response.status()) { + case 401 -> new CustomFeignClientException(ErrorCode.FEIGN_BAD_REQUEST); + case 402 -> new CustomFeignClientException(ErrorCode.FEIGN_UNAUTHORIZED); + case 403 -> new CustomFeignClientException(ErrorCode.FEIGN_FORBIDDEN); + case 404 -> new CustomFeignClientException(ErrorCode.FEIGN_EXPIRED_TOKEN); + case 405 -> new CustomFeignClientException(ErrorCode.FEIGN_NOT_FOUND); + case 406 -> new CustomFeignClientException(ErrorCode.FEIGN_INTERNAL_SERVER_ERROR); + case 407 -> new CustomFeignClientException(ErrorCode.FEIGN_METHOD_NOT_ALLOWED); + default -> throw new IllegalStateException( + "Unexpected value: " + response.status()); }; - } - else{ + } else { logger.error("feign client 500๋ฒˆ๋Œ€ ์—๋Ÿฌ ๋ฐœ์ƒ : {}", response.reason()); return new CustomFeignClientException(ErrorCode.FEIGN_SERVER_ERROR); } diff --git a/src/main/java/briefing/feign/oauth/apple/client/AppleOauth2Client.java b/src/main/java/briefing/feign/oauth/apple/client/AppleOauth2Client.java index 6f55cdd..1122b26 100644 --- a/src/main/java/briefing/feign/oauth/apple/client/AppleOauth2Client.java +++ b/src/main/java/briefing/feign/oauth/apple/client/AppleOauth2Client.java @@ -1,12 +1,16 @@ package briefing.feign.oauth.apple.client; -import briefing.feign.oauth.apple.config.AppleOauth2FeignConfiguration; -import briefing.feign.oauth.apple.dto.ApplePublicKeyList; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; -@FeignClient(name = "apple-public-key-client", url = "https://appleid.apple.com/auth",configuration = AppleOauth2FeignConfiguration.class) +import briefing.feign.oauth.apple.config.AppleOauth2FeignConfiguration; +import briefing.feign.oauth.apple.dto.ApplePublicKeyList; + +@FeignClient( + name = "apple-public-key-client", + url = "https://appleid.apple.com/auth", + configuration = AppleOauth2FeignConfiguration.class) @Component public interface AppleOauth2Client { @GetMapping("/keys") diff --git a/src/main/java/briefing/feign/oauth/apple/config/AppleOauth2FeignConfiguration.java b/src/main/java/briefing/feign/oauth/apple/config/AppleOauth2FeignConfiguration.java index bb2e5a3..4716b3d 100644 --- a/src/main/java/briefing/feign/oauth/apple/config/AppleOauth2FeignConfiguration.java +++ b/src/main/java/briefing/feign/oauth/apple/config/AppleOauth2FeignConfiguration.java @@ -1,20 +1,21 @@ package briefing.feign.oauth.apple.config; +import org.springframework.context.annotation.Bean; + import briefing.feign.exception.FeignClientExceptionErrorDecoder; import feign.Logger; import feign.RequestInterceptor; import feign.codec.ErrorDecoder; -import org.springframework.context.annotation.Bean; public class AppleOauth2FeignConfiguration { @Bean - public RequestInterceptor requestInterceptor(){ + public RequestInterceptor requestInterceptor() { return template -> template.header("Content-Type", "application/json;charset=UTF-8"); } @Bean public ErrorDecoder errorDecoder() { - return new FeignClientExceptionErrorDecoder(); + return new FeignClientExceptionErrorDecoder(); } @Bean diff --git a/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKey.java b/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKey.java index e01f650..247cb07 100644 --- a/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKey.java +++ b/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKey.java @@ -11,14 +11,26 @@ public class ApplePublicKey { @Override public String toString() { - return "ApplePublicKeyDTO{" + - "kty='" + kty + '\'' + - ", kid='" + kid + '\'' + - ", use='" + use + '\'' + - ", alg='" + alg + '\'' + - ", n='" + n + '\'' + - ", e='" + e + '\'' + - '}'; + return "ApplePublicKeyDTO{" + + "kty='" + + kty + + '\'' + + ", kid='" + + kid + + '\'' + + ", use='" + + use + + '\'' + + ", alg='" + + alg + + '\'' + + ", n='" + + n + + '\'' + + ", e='" + + e + + '\'' + + '}'; } private String kty; diff --git a/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKeyList.java b/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKeyList.java index 3cd60aa..2d5b1a5 100644 --- a/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKeyList.java +++ b/src/main/java/briefing/feign/oauth/apple/dto/ApplePublicKeyList.java @@ -1,13 +1,13 @@ package briefing.feign.oauth.apple.dto; +import java.util.List; + import briefing.exception.ErrorCode; import briefing.exception.handler.AppleOAuthException; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.util.List; - @Getter @Setter @NoArgsConstructor @@ -15,8 +15,7 @@ public class ApplePublicKeyList { private List keys; public ApplePublicKey getMatchesKey(String alg, String kid) { - return this.keys - .stream() + return this.keys.stream() .filter(k -> k.getAlg().equals(alg) && k.getKid().equals(kid)) .findFirst() .orElseThrow(() -> new AppleOAuthException(ErrorCode.APPLE_BAD_REQUEST)); diff --git a/src/main/java/briefing/feign/oauth/google/client/GoogleOauth2Client.java b/src/main/java/briefing/feign/oauth/google/client/GoogleOauth2Client.java index 12bc11e..5b709a8 100644 --- a/src/main/java/briefing/feign/oauth/google/client/GoogleOauth2Client.java +++ b/src/main/java/briefing/feign/oauth/google/client/GoogleOauth2Client.java @@ -1,16 +1,16 @@ package briefing.feign.oauth.google.client; -import briefing.feign.oauth.google.config.GoogleOauth2FeignConfiguration; -import briefing.feign.oauth.google.dto.GoogleUserInfo; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import briefing.feign.oauth.google.config.GoogleOauth2FeignConfiguration; +import briefing.feign.oauth.google.dto.GoogleUserInfo; + @FeignClient( name = "googleOauth2Client", url = "https://www.googleapis.com/oauth2/v3", - configuration = GoogleOauth2FeignConfiguration.class -) + configuration = GoogleOauth2FeignConfiguration.class) public interface GoogleOauth2Client { @GetMapping("/tokeninfo") GoogleUserInfo verifyToken(@RequestParam("id_token") String id_token); diff --git a/src/main/java/briefing/feign/oauth/google/config/GoogleOauth2FeignConfiguration.java b/src/main/java/briefing/feign/oauth/google/config/GoogleOauth2FeignConfiguration.java index ba10094..b8cd06c 100644 --- a/src/main/java/briefing/feign/oauth/google/config/GoogleOauth2FeignConfiguration.java +++ b/src/main/java/briefing/feign/oauth/google/config/GoogleOauth2FeignConfiguration.java @@ -1,14 +1,15 @@ package briefing.feign.oauth.google.config; +import org.springframework.context.annotation.Bean; + import briefing.feign.exception.FeignClientExceptionErrorDecoder; import feign.Logger; import feign.RequestInterceptor; import feign.codec.ErrorDecoder; -import org.springframework.context.annotation.Bean; public class GoogleOauth2FeignConfiguration { @Bean - public RequestInterceptor requestInterceptor(){ + public RequestInterceptor requestInterceptor() { return template -> template.header("Content-Type", "application/json; charset=UTF-8"); } diff --git a/src/main/java/briefing/feign/oauth/google/dto/GoogleUserInfo.java b/src/main/java/briefing/feign/oauth/google/dto/GoogleUserInfo.java index 1b1c50b..f378681 100644 --- a/src/main/java/briefing/feign/oauth/google/dto/GoogleUserInfo.java +++ b/src/main/java/briefing/feign/oauth/google/dto/GoogleUserInfo.java @@ -16,4 +16,4 @@ public class GoogleUserInfo { private String family_name; private String locale; // ํ•„์š”ํ•œ ๋‹ค๋ฅธ ํ•„๋“œ๋„ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ -} \ No newline at end of file +} diff --git a/src/main/java/briefing/member/api/MemberApi.java b/src/main/java/briefing/member/api/MemberApi.java index e19d8d7..ae8aac9 100644 --- a/src/main/java/briefing/member/api/MemberApi.java +++ b/src/main/java/briefing/member/api/MemberApi.java @@ -1,5 +1,14 @@ package briefing.member.api; +import java.util.Arrays; +import java.util.List; + +import jakarta.validation.Valid; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + import briefing.common.response.CommonResponse; import briefing.member.application.MemberCommandService; import briefing.member.application.MemberQueryService; @@ -21,21 +30,17 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Arrays; -import java.util.List; -@Tag(name = "02-Member \uD83D\uDC64",description = "์‚ฌ์šฉ์ž ๊ด€๋ จ API") +@Tag(name = "02-Member \uD83D\uDC64", description = "์‚ฌ์šฉ์ž ๊ด€๋ จ API") @RestController @Validated @RequiredArgsConstructor @ApiResponses({ - @ApiResponse(responseCode = "COMMON000", description = "SERVER ERROR, ๋ฐฑ์•ค๋“œ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์•Œ๋ ค์ฃผ์„ธ์š”", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "COMMON000", + description = "SERVER ERROR, ๋ฐฑ์•ค๋“œ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์•Œ๋ ค์ฃผ์„ธ์š”", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) public class MemberApi { private final MemberQueryService memberQueryService; @@ -47,82 +52,161 @@ public class MemberApi { @Operation(summary = "Member\uD83D\uDC64 ํ† ํฐ ์ž˜ ๋ฐœ๊ธ‰๋˜๋‚˜ ํ…Œ์ŠคํŠธ์šฉAPI", description = "ํ…Œ์ŠคํŠธ ์šฉ") @GetMapping("/members/auth/test") - public CommonResponse testGenerateToken(){ + public CommonResponse testGenerateToken() { Member member = memberQueryService.testForTokenApi(); - String accessToken = tokenProvider.createAccessToken(member.getId(), member.getSocialType().toString() ,member.getSocialId(), Arrays.asList(new SimpleGrantedAuthority(member.getRole().name()))); - RefreshToken refreshToken = redisService.generateRefreshToken(member.getSocialId(), member.getSocialType()); - return CommonResponse.onSuccess(MemberConverter.toLoginDTO(member,accessToken, refreshToken.getToken())); + String accessToken = + tokenProvider.createAccessToken( + member.getId(), + member.getSocialType().toString(), + member.getSocialId(), + Arrays.asList(new SimpleGrantedAuthority(member.getRole().name()))); + RefreshToken refreshToken = + redisService.generateRefreshToken(member.getSocialId(), member.getSocialType()); + return CommonResponse.onSuccess( + MemberConverter.toLoginDTO(member, accessToken, refreshToken.getToken())); } @Operation(summary = "02-01 Member\uD83D\uDC64 ์†Œ์…œ ๋กœ๊ทธ์ธ V1", description = "๊ตฌ๊ธ€, ์• ํ”Œ ์†Œ์…œ๋กœ๊ทธ์ธ API์ž…๋‹ˆ๋‹ค.") @PostMapping("/members/auth/{socialType}") public CommonResponse login( - @Parameter(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ข…๋ฅ˜", example = "google") @PathVariable final SocialType socialType, - @RequestBody final MemberRequest.LoginDTO request - ) { + @Parameter(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ข…๋ฅ˜", example = "google") @PathVariable + final SocialType socialType, + @RequestBody final MemberRequest.LoginDTO request) { Member member = memberCommandService.login(socialType, request); - String accessToken = tokenProvider.createAccessToken(member.getId(),member.getSocialType().toString() ,member.getSocialId(), List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name()))); - String refreshToken = redisService.generateRefreshToken(member.getSocialId(),member.getSocialType()).getToken(); - return CommonResponse.onSuccess(MemberConverter.toLoginDTO(member, accessToken, refreshToken)); + String accessToken = + tokenProvider.createAccessToken( + member.getId(), + member.getSocialType().toString(), + member.getSocialId(), + List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name()))); + String refreshToken = + redisService + .generateRefreshToken(member.getSocialId(), member.getSocialType()) + .getToken(); + return CommonResponse.onSuccess( + MemberConverter.toLoginDTO(member, accessToken, refreshToken)); } @Operation(summary = "02-01 Member\uD83D\uDC64 ์†Œ์…œ ๋กœ๊ทธ์ธ V2", description = "๊ตฌ๊ธ€, ์• ํ”Œ ์†Œ์…œ๋กœ๊ทธ์ธ API์ž…๋‹ˆ๋‹ค.") @PostMapping("/v2/members/auth/{socialType}") public CommonResponse loginV2( - @Parameter(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ข…๋ฅ˜", example = "google") @PathVariable final SocialType socialType, - @RequestBody final MemberRequest.LoginDTO request - ) { + @Parameter(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ข…๋ฅ˜", example = "google") @PathVariable + final SocialType socialType, + @RequestBody final MemberRequest.LoginDTO request) { Member member = memberCommandService.login(socialType, request); - String accessToken = tokenProvider.createAccessToken(member.getId(),member.getSocialType().toString() ,member.getSocialId(), List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name()))); - String refreshToken = redisService.generateRefreshToken(member.getSocialId(),member.getSocialType()).getToken(); - return CommonResponse.onSuccess(MemberConverter.toLoginDTO(member, accessToken, refreshToken)); + String accessToken = + tokenProvider.createAccessToken( + member.getId(), + member.getSocialType().toString(), + member.getSocialId(), + List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name()))); + String refreshToken = + redisService + .generateRefreshToken(member.getSocialId(), member.getSocialType()) + .getToken(); + return CommonResponse.onSuccess( + MemberConverter.toLoginDTO(member, accessToken, refreshToken)); } - @Operation(summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V1", description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") + @Operation( + summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V1", + description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") @ApiResponses({ - @ApiResponse(responseCode = "1000",description = "OK, ์„ฑ๊ณต"), - @ApiResponse(responseCode = "COMMON001", description = "request body์— ๋‹ด๊ธธ ๊ฐ’์ด ์ด์ƒํ•จ, result๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH005", description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ, ๋‹ค์‹œ ๋กœ๊ทธ์ธ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH009", description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ชจ์–‘์ด ์ž˜๋ชป ๋จ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), + @ApiResponse( + responseCode = "COMMON001", + description = "request body์— ๋‹ด๊ธธ ๊ฐ’์ด ์ด์ƒํ•จ, result๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH005", + description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ, ๋‹ค์‹œ ๋กœ๊ทธ์ธ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH009", + description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ชจ์–‘์ด ์ž˜๋ชป ๋จ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) @PostMapping("/members/auth/token") - public CommonResponse reissueToken(@Valid @RequestBody MemberRequest.ReissueDTO request){ + public CommonResponse reissueToken( + @Valid @RequestBody MemberRequest.ReissueDTO request) { RefreshToken refreshToken = redisService.reGenerateRefreshToken(request); Member parsedMember = memberCommandService.parseRefreshToken(refreshToken); - String accessToken = tokenProvider.createAccessToken(parsedMember.getId(),parsedMember.getSocialType().toString(), parsedMember.getSocialId(), List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString()))); - return CommonResponse.onSuccess(MemberConverter.toReIssueTokenDTO(parsedMember.getId(), accessToken,refreshToken.getToken())); + String accessToken = + tokenProvider.createAccessToken( + parsedMember.getId(), + parsedMember.getSocialType().toString(), + parsedMember.getSocialId(), + List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString()))); + return CommonResponse.onSuccess( + MemberConverter.toReIssueTokenDTO( + parsedMember.getId(), accessToken, refreshToken.getToken())); } - @Operation(summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V2", description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") + @Operation( + summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V2", + description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") @ApiResponses({ - @ApiResponse(responseCode = "1000",description = "OK, ์„ฑ๊ณต"), - @ApiResponse(responseCode = "COMMON001", description = "request body์— ๋‹ด๊ธธ ๊ฐ’์ด ์ด์ƒํ•จ, result๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH005", description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ, ๋‹ค์‹œ ๋กœ๊ทธ์ธ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH009", description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ชจ์–‘์ด ์ž˜๋ชป ๋จ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), + @ApiResponse( + responseCode = "COMMON001", + description = "request body์— ๋‹ด๊ธธ ๊ฐ’์ด ์ด์ƒํ•จ, result๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH005", + description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ, ๋‹ค์‹œ ๋กœ๊ทธ์ธ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH009", + description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ชจ์–‘์ด ์ž˜๋ชป ๋จ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) @PostMapping("/v2/members/auth/token") - public CommonResponse reissueTokenV2(@Valid @RequestBody MemberRequest.ReissueDTO request){ + public CommonResponse reissueTokenV2( + @Valid @RequestBody MemberRequest.ReissueDTO request) { RefreshToken refreshToken = redisService.reGenerateRefreshToken(request); Member parsedMember = memberCommandService.parseRefreshToken(refreshToken); - String accessToken = tokenProvider.createAccessToken(parsedMember.getId(),parsedMember.getSocialType().toString(), parsedMember.getSocialId(), List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString()))); - return CommonResponse.onSuccess(MemberConverter.toReIssueTokenDTO(parsedMember.getId(), accessToken,refreshToken.getToken())); + String accessToken = + tokenProvider.createAccessToken( + parsedMember.getId(), + parsedMember.getSocialType().toString(), + parsedMember.getSocialId(), + List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString()))); + return CommonResponse.onSuccess( + MemberConverter.toReIssueTokenDTO( + parsedMember.getId(), accessToken, refreshToken.getToken())); } @Operation(summary = "02-01 Member\uD83D\uDC64 ํšŒ์› ํƒˆํ‡ด V1", description = "ํšŒ์› ํƒˆํ‡ด API ์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/members/{memberId}") @Parameters({ - @Parameter(name = "member", hidden = true), - @Parameter(name = "memberId", description = "์‚ญ์ œ ๋Œ€์ƒ ๋ฉค๋ฒ„์•„์ด๋””") + @Parameter(name = "member", hidden = true), + @Parameter(name = "memberId", description = "์‚ญ์ œ ๋Œ€์ƒ ๋ฉค๋ฒ„์•„์ด๋””") }) @ApiResponses({ - @ApiResponse(responseCode = "1000",description = "OK, ์„ฑ๊ณต"), - @ApiResponse(responseCode = "AUTH003", description = "access ํ† ํฐ์„ ์ฃผ์„ธ์š”!",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH004", description = "acess ํ† ํฐ ๋งŒ๋ฃŒ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH006", description = "acess ํ† ํฐ ๋ชจ์–‘์ด ์ด์ƒํ•จ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "MEMBER_001", description = "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "MEMBER_002", description = "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ํƒˆํ‡ด ๋Œ€์ƒ์ด ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), + @ApiResponse( + responseCode = "AUTH003", + description = "access ํ† ํฐ์„ ์ฃผ์„ธ์š”!", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH004", + description = "acess ํ† ํฐ ๋งŒ๋ฃŒ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH006", + description = "acess ํ† ํฐ ๋ชจ์–‘์ด ์ด์ƒํ•จ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "MEMBER_001", + description = "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "MEMBER_002", + description = "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ํƒˆํ‡ด ๋Œ€์ƒ์ด ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - public CommonResponse quitMember(@AuthMember Member member, @CheckSameMember @PathVariable Long memberId){ + public CommonResponse quitMember( + @AuthMember Member member, @CheckSameMember @PathVariable Long memberId) { memberCommandService.deleteMember(memberId); return CommonResponse.onSuccess(MemberConverter.toQuitDTO()); } @@ -130,18 +214,34 @@ public CommonResponse quitMember(@AuthMember Member memb @Operation(summary = "02-01 Member\uD83D\uDC64 ํšŒ์› ํƒˆํ‡ด V2", description = "ํšŒ์› ํƒˆํ‡ด API ์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/v2/members/{memberId}") @Parameters({ - @Parameter(name = "member", hidden = true), - @Parameter(name = "memberId", description = "์‚ญ์ œ ๋Œ€์ƒ ๋ฉค๋ฒ„์•„์ด๋””") + @Parameter(name = "member", hidden = true), + @Parameter(name = "memberId", description = "์‚ญ์ œ ๋Œ€์ƒ ๋ฉค๋ฒ„์•„์ด๋””") }) @ApiResponses({ - @ApiResponse(responseCode = "1000",description = "OK, ์„ฑ๊ณต"), - @ApiResponse(responseCode = "AUTH003", description = "access ํ† ํฐ์„ ์ฃผ์„ธ์š”!",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH004", description = "acess ํ† ํฐ ๋งŒ๋ฃŒ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "AUTH006", description = "acess ํ† ํฐ ๋ชจ์–‘์ด ์ด์ƒํ•จ",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "MEMBER_001", description = "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "MEMBER_002", description = "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ํƒˆํ‡ด ๋Œ€์ƒ์ด ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), + @ApiResponse( + responseCode = "AUTH003", + description = "access ํ† ํฐ์„ ์ฃผ์„ธ์š”!", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH004", + description = "acess ํ† ํฐ ๋งŒ๋ฃŒ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH006", + description = "acess ํ† ํฐ ๋ชจ์–‘์ด ์ด์ƒํ•จ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "MEMBER_001", + description = "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "MEMBER_002", + description = "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ํƒˆํ‡ด ๋Œ€์ƒ์ด ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - public CommonResponse quitMemberV2(@AuthMember Member member, @CheckSameMember @PathVariable Long memberId){ + public CommonResponse quitMemberV2( + @AuthMember Member member, @CheckSameMember @PathVariable Long memberId) { memberCommandService.deleteMember(memberId); return CommonResponse.onSuccess(MemberConverter.toQuitDTO()); } diff --git a/src/main/java/briefing/member/api/MemberConverter.java b/src/main/java/briefing/member/api/MemberConverter.java index e327b07..b84aa89 100644 --- a/src/main/java/briefing/member/api/MemberConverter.java +++ b/src/main/java/briefing/member/api/MemberConverter.java @@ -1,25 +1,23 @@ package briefing.member.api; -import briefing.exception.ErrorCode; -import briefing.exception.handler.MemberException; +import java.time.LocalDateTime; + +import org.springframework.stereotype.Component; + import briefing.feign.oauth.google.dto.GoogleUserInfo; import briefing.member.application.dto.MemberResponse; import briefing.member.domain.Member; import briefing.member.domain.MemberRole; import briefing.member.domain.MemberStatus; import briefing.member.domain.SocialType; -import briefing.member.domain.repository.MemberRepository; -import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; @RequiredArgsConstructor @Component public class MemberConverter { - public static MemberResponse.LoginDTO toLoginDTO(Member member, String accessToken, String refreshToken) { + public static MemberResponse.LoginDTO toLoginDTO( + Member member, String accessToken, String refreshToken) { return MemberResponse.LoginDTO.builder() .memberId(member.getId()) .accessToken(accessToken) @@ -40,8 +38,8 @@ public static Member toMember(GoogleUserInfo googleUserInfo) { public static Member toMember(String appleSocialId) { return Member.builder() -// .profileImgUrl(googleUserInfo.getPicture()) -// .nickName(googleUserInfo.getName()) + // .profileImgUrl(googleUserInfo.getPicture()) + // .nickName(googleUserInfo.getName()) .socialId(appleSocialId) .socialType(SocialType.APPLE) .role(MemberRole.ROLE_USER) @@ -49,7 +47,8 @@ public static Member toMember(String appleSocialId) { .build(); } - public static MemberResponse.ReIssueTokenDTO toReIssueTokenDTO(Long memberId,String accessToken, String refreshToken){ + public static MemberResponse.ReIssueTokenDTO toReIssueTokenDTO( + Long memberId, String accessToken, String refreshToken) { return MemberResponse.ReIssueTokenDTO.builder() .memberId(memberId) .accessToken(accessToken) @@ -57,9 +56,7 @@ public static MemberResponse.ReIssueTokenDTO toReIssueTokenDTO(Long memberId,Str .build(); } - public static MemberResponse.QuitDTO toQuitDTO(){ - return MemberResponse.QuitDTO.builder() - .quitAt(LocalDateTime.now()) - .build(); + public static MemberResponse.QuitDTO toQuitDTO() { + return MemberResponse.QuitDTO.builder().quitAt(LocalDateTime.now()).build(); } } diff --git a/src/main/java/briefing/member/application/MemberCommandService.java b/src/main/java/briefing/member/application/MemberCommandService.java index 4e848b2..bfe7899 100644 --- a/src/main/java/briefing/member/application/MemberCommandService.java +++ b/src/main/java/briefing/member/application/MemberCommandService.java @@ -1,5 +1,20 @@ package briefing.member.application; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.Optional; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.exception.ErrorCode; import briefing.exception.handler.AppleOAuthException; import briefing.exception.handler.MemberException; @@ -11,8 +26,6 @@ import briefing.member.api.MemberConverter; import briefing.member.application.dto.MemberRequest; import briefing.member.domain.Member; -import briefing.member.domain.MemberRole; -import briefing.member.domain.MemberStatus; import briefing.member.domain.SocialType; import briefing.member.domain.repository.MemberRepository; import briefing.redis.domain.RefreshToken; @@ -20,21 +33,6 @@ import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.spec.RSAPublicKeySpec; -import java.util.Base64; -import java.util.Optional; - @Service @Transactional @RequiredArgsConstructor @@ -47,7 +45,6 @@ public class MemberCommandService { Logger logger = LoggerFactory.getLogger(MemberCommandService.class); - public Member login(SocialType socialType, MemberRequest.LoginDTO request) { return switch (socialType) { case GOOGLE -> loginWithGoogle(request); @@ -59,8 +56,10 @@ private Member loginWithGoogle(MemberRequest.LoginDTO request) { // ๊ตฌ๊ธ€์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ GoogleUserInfo googleUserInfo = googleOauth2Client.verifyToken(request.getIdentityToken()); - Member member = memberRepository.findBySocialIdAndSocialType(googleUserInfo.getSub(), SocialType.GOOGLE) - .orElseGet(() -> MemberConverter.toMember(googleUserInfo)); + Member member = + memberRepository + .findBySocialIdAndSocialType(googleUserInfo.getSub(), SocialType.GOOGLE) + .orElseGet(() -> MemberConverter.toMember(googleUserInfo)); return memberRepository.save(member); } @@ -86,8 +85,9 @@ private Member loginWithApple(MemberRequest.LoginDTO request) { Object kid = headerJson.get("kid"); Object alg = headerJson.get("alg"); - applePublicKey = applePublicKeys.getMatchesKey(String.valueOf(alg), String.valueOf(kid)); - }catch (ParseException e){ + applePublicKey = + applePublicKeys.getMatchesKey(String.valueOf(alg), String.valueOf(kid)); + } catch (ParseException e) { e.printStackTrace(); } @@ -95,16 +95,24 @@ private Member loginWithApple(MemberRequest.LoginDTO request) { System.out.println(applePublicKey.toString()); PublicKey publicKey = this.getPublicKey(applePublicKey); - Claims userInfo = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(request.getIdentityToken()).getBody(); + Claims userInfo = + Jwts.parserBuilder() + .setSigningKey(publicKey) + .build() + .parseClaimsJws(request.getIdentityToken()) + .getBody(); System.out.println("ํŒŒ์‹ฑ๋œ ์œ ์ €์˜ ์ •๋ณด"); System.out.println(userInfo); String appleSocialId = userInfo.get("sub", String.class); - Optional foundMember = memberRepository.findBySocialIdAndSocialType(appleSocialId, SocialType.APPLE); + Optional foundMember = + memberRepository.findBySocialIdAndSocialType(appleSocialId, SocialType.APPLE); - return foundMember.isEmpty() ? memberRepository.save(MemberConverter.toMember(appleSocialId)) : foundMember.get(); + return foundMember.isEmpty() + ? memberRepository.save(MemberConverter.toMember(appleSocialId)) + : foundMember.get(); } private PublicKey getPublicKey(ApplePublicKey applePublicKeyDTO) { @@ -130,11 +138,13 @@ private PublicKey getPublicKey(ApplePublicKey applePublicKeyDTO) { } } - public Member parseRefreshToken(RefreshToken refreshToken){ - return memberRepository.findById(refreshToken.getMemberId()).orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + public Member parseRefreshToken(RefreshToken refreshToken) { + return memberRepository + .findById(refreshToken.getMemberId()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); } - public void deleteMember(Long memberId){ + public void deleteMember(Long memberId) { memberRepository.deleteById(memberId); } } diff --git a/src/main/java/briefing/member/application/MemberQueryService.java b/src/main/java/briefing/member/application/MemberQueryService.java index 600e397..43c0592 100644 --- a/src/main/java/briefing/member/application/MemberQueryService.java +++ b/src/main/java/briefing/member/application/MemberQueryService.java @@ -1,5 +1,8 @@ package briefing.member.application; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.exception.ErrorCode; import briefing.exception.handler.MemberException; import briefing.member.domain.Member; @@ -7,10 +10,6 @@ import briefing.member.domain.SocialType; import briefing.member.domain.repository.MemberRepository; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Optional; @Service @Transactional(readOnly = true) @@ -18,15 +17,24 @@ public class MemberQueryService { private final MemberRepository memberRepository; - public Member findById(Long memberId){ - return memberRepository.findById(memberId).orElseThrow(()->new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + public Member findById(Long memberId) { + return memberRepository + .findById(memberId) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); } @Transactional - public Member testForTokenApi(){ - return memberRepository.findFirstByOrderByCreatedAt().orElseGet(()-> - memberRepository.save(Member.builder().nickName(",,,!,1").socialId("1234567").socialType(SocialType.GOOGLE).role(MemberRole.ROLE_USER) - .build()) - ); + public Member testForTokenApi() { + return memberRepository + .findFirstByOrderByCreatedAt() + .orElseGet( + () -> + memberRepository.save( + Member.builder() + .nickName(",,,!,1") + .socialId("1234567") + .socialType(SocialType.GOOGLE) + .role(MemberRole.ROLE_USER) + .build())); } } diff --git a/src/main/java/briefing/member/application/dto/MemberRequest.java b/src/main/java/briefing/member/application/dto/MemberRequest.java index d31fc0a..42a21e7 100644 --- a/src/main/java/briefing/member/application/dto/MemberRequest.java +++ b/src/main/java/briefing/member/application/dto/MemberRequest.java @@ -1,6 +1,7 @@ package briefing.member.application.dto; import jakarta.validation.constraints.NotBlank; + import lombok.Getter; public class MemberRequest { @@ -11,8 +12,7 @@ public static class LoginDTO { } @Getter - public static class ReissueDTO{ - @NotBlank - private String refreshToken; + public static class ReissueDTO { + @NotBlank private String refreshToken; } } diff --git a/src/main/java/briefing/member/application/dto/MemberResponse.java b/src/main/java/briefing/member/application/dto/MemberResponse.java index 564b7e7..45d9055 100644 --- a/src/main/java/briefing/member/application/dto/MemberResponse.java +++ b/src/main/java/briefing/member/application/dto/MemberResponse.java @@ -1,17 +1,17 @@ package briefing.member.application.dto; +import java.time.LocalDateTime; +import java.util.List; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.data.redis.connection.lettuce.observability.LettuceObservationContext; - -import java.time.LocalDateTime; -import java.util.List; public class MemberResponse { - @Builder @Getter + @Builder + @Getter @NoArgsConstructor @AllArgsConstructor public static class LoginDTO { @@ -20,25 +20,29 @@ public static class LoginDTO { private String refreshToken; } - @Builder @Getter + @Builder + @Getter @NoArgsConstructor @AllArgsConstructor - public static class ReIssueTokenDTO{ + public static class ReIssueTokenDTO { private Long memberId; private String accessToken; private String refreshToken; } - @Builder @Getter + + @Builder + @Getter @NoArgsConstructor @AllArgsConstructor - public static class ListReIssueTokenDTO{ + public static class ListReIssueTokenDTO { List ReIssueTokenList; } - @Builder @Getter + @Builder + @Getter @NoArgsConstructor @AllArgsConstructor - public static class QuitDTO{ + public static class QuitDTO { private LocalDateTime quitAt; } } diff --git a/src/main/java/briefing/member/domain/Member.java b/src/main/java/briefing/member/domain/Member.java index 93e6722..72fef5f 100644 --- a/src/main/java/briefing/member/domain/Member.java +++ b/src/main/java/briefing/member/domain/Member.java @@ -1,15 +1,17 @@ package briefing.member.domain; +import java.util.ArrayList; +import java.util.List; + +import jakarta.persistence.*; + import briefing.base.BaseDateTimeEntity; import briefing.scrap.domain.Scrap; -import jakarta.persistence.*; import lombok.*; -import java.util.ArrayList; -import java.util.List; - @Entity -@Getter @Builder +@Getter +@Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Member extends BaseDateTimeEntity { diff --git a/src/main/java/briefing/member/domain/SocialType.java b/src/main/java/briefing/member/domain/SocialType.java index bacbfc6..49b73b8 100644 --- a/src/main/java/briefing/member/domain/SocialType.java +++ b/src/main/java/briefing/member/domain/SocialType.java @@ -1,13 +1,13 @@ package briefing.member.domain; import com.fasterxml.jackson.annotation.JsonValue; + import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum SocialType { - GOOGLE("google", "๊ตฌ๊ธ€"), APPLE("apple", "์• ํ”Œ"); @@ -22,6 +22,7 @@ public static SocialType findByValue(String value) { } throw new IllegalArgumentException("Invalid SocialType value: " + value); } + @JsonValue String getSocialType() { return this.name().toLowerCase(); diff --git a/src/main/java/briefing/member/domain/repository/MemberRepository.java b/src/main/java/briefing/member/domain/repository/MemberRepository.java index dbf6935..16031ee 100644 --- a/src/main/java/briefing/member/domain/repository/MemberRepository.java +++ b/src/main/java/briefing/member/domain/repository/MemberRepository.java @@ -1,10 +1,11 @@ package briefing.member.domain.repository; -import briefing.member.domain.Member; -import briefing.member.domain.SocialType; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; +import briefing.member.domain.Member; +import briefing.member.domain.SocialType; public interface MemberRepository extends JpaRepository { Optional findBySocialIdAndSocialType(String socialId, SocialType socialType); diff --git a/src/main/java/briefing/member/exception/MemberException.java b/src/main/java/briefing/member/exception/MemberException.java index 4bf588d..0b7241d 100644 --- a/src/main/java/briefing/member/exception/MemberException.java +++ b/src/main/java/briefing/member/exception/MemberException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class MemberException extends GeneralException { - public MemberException(ErrorCode errorCode){ + public MemberException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/redis/domain/RefreshToken.java b/src/main/java/briefing/redis/domain/RefreshToken.java index a94a493..6f22736 100644 --- a/src/main/java/briefing/redis/domain/RefreshToken.java +++ b/src/main/java/briefing/redis/domain/RefreshToken.java @@ -1,10 +1,11 @@ package briefing.redis.domain; -import lombok.*; +import java.time.LocalDateTime; + import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; -import java.time.LocalDateTime; +import lombok.*; @RedisHash(value = "refreshToken_Breifing", timeToLive = 1800000) @Builder @@ -13,8 +14,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class RefreshToken { - @Id - private String token; + @Id private String token; private Long memberId; diff --git a/src/main/java/briefing/redis/repository/RefreshTokenRepository.java b/src/main/java/briefing/redis/repository/RefreshTokenRepository.java index b426170..ffb7b3b 100644 --- a/src/main/java/briefing/redis/repository/RefreshTokenRepository.java +++ b/src/main/java/briefing/redis/repository/RefreshTokenRepository.java @@ -1,10 +1,7 @@ package briefing.redis.repository; -import briefing.redis.domain.RefreshToken; import org.springframework.data.repository.CrudRepository; -import java.util.Optional; - -public interface RefreshTokenRepository extends CrudRepository { +import briefing.redis.domain.RefreshToken; -} +public interface RefreshTokenRepository extends CrudRepository {} diff --git a/src/main/java/briefing/redis/service/RedisService.java b/src/main/java/briefing/redis/service/RedisService.java index 1472a5f..72c172c 100644 --- a/src/main/java/briefing/redis/service/RedisService.java +++ b/src/main/java/briefing/redis/service/RedisService.java @@ -1,7 +1,6 @@ package briefing.redis.service; import briefing.member.application.dto.MemberRequest; -import briefing.member.domain.Member; import briefing.member.domain.SocialType; import briefing.redis.domain.RefreshToken; diff --git a/src/main/java/briefing/redis/service/RedisServiceImpl.java b/src/main/java/briefing/redis/service/RedisServiceImpl.java index fb32967..6cdad9b 100644 --- a/src/main/java/briefing/redis/service/RedisServiceImpl.java +++ b/src/main/java/briefing/redis/service/RedisServiceImpl.java @@ -1,5 +1,15 @@ package briefing.redis.service; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.exception.ErrorCode; import briefing.exception.handler.MemberException; import briefing.exception.handler.RefreshTokenException; @@ -10,22 +20,13 @@ import briefing.redis.domain.RefreshToken; import briefing.redis.repository.RefreshTokenRepository; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Optional; -import java.util.UUID; @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class RedisServiceImpl implements RedisService{ +public class RedisServiceImpl implements RedisService { - Logger logger =LoggerFactory.getLogger(RedisServiceImpl.class); + Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class); private final MemberRepository memberRepository; @@ -34,48 +35,57 @@ public class RedisServiceImpl implements RedisService{ @Override @Transactional public RefreshToken generateRefreshToken(String socialId, SocialType socialType) { - Member member = memberRepository.findBySocialIdAndSocialType(socialId, socialType).orElseThrow(() -> new RefreshTokenException(ErrorCode.MEMBER_NOT_FOUND)); + Member member = + memberRepository + .findBySocialIdAndSocialType(socialId, socialType) + .orElseThrow(() -> new RefreshTokenException(ErrorCode.MEMBER_NOT_FOUND)); // ์ด ๋ถ€๋ถ„ ๊ดœ์ฐฎ์€์ง€ ๋ฆฌ๋ทฐ String token = UUID.randomUUID().toString(); Long memberId = member.getId(); - LocalDateTime currentTime =LocalDateTime.now(); + LocalDateTime currentTime = LocalDateTime.now(); LocalDateTime expireTime = currentTime.plus(1000, ChronoUnit.MINUTES); return refreshTokenRepository.save( - RefreshToken.builder() - .memberId(memberId) - .token(token) - .expireTime(expireTime).build() - ); + RefreshToken.builder() + .memberId(memberId) + .token(token) + .expireTime(expireTime) + .build()); } @Override public RefreshToken reGenerateRefreshToken(MemberRequest.ReissueDTO request) { - if(request.getRefreshToken() == null) + if (request.getRefreshToken() == null) throw new MemberException(ErrorCode.INVALID_TOKEN_EXCEPTION); - RefreshToken findRefreshToken = refreshTokenRepository.findById(request.getRefreshToken()).orElseThrow(() -> new RefreshTokenException(ErrorCode.INVALID_REFRESH_TOKEN)); + RefreshToken findRefreshToken = + refreshTokenRepository + .findById(request.getRefreshToken()) + .orElseThrow( + () -> new RefreshTokenException(ErrorCode.INVALID_REFRESH_TOKEN)); LocalDateTime expireTime = findRefreshToken.getExpireTime(); LocalDateTime current = LocalDateTime.now(); // ํ…Œ์ŠคํŠธ์šฉ, ์‹ค์ œ๋กœ๋Š” ํ˜„์žฌ ์‹œ๊ฐ„ + accessToken ๋งŒ๋ฃŒ ์‹œ๊ฐ„ LocalDateTime expireDeadLine = current.plusSeconds(20); - Member member = memberRepository.findById(findRefreshToken.getMemberId()).orElseThrow(() -> new RefreshTokenException(ErrorCode.MEMBER_NOT_FOUND)); + Member member = + memberRepository + .findById(findRefreshToken.getMemberId()) + .orElseThrow(() -> new RefreshTokenException(ErrorCode.MEMBER_NOT_FOUND)); - if(current.isAfter(expireTime)) { + if (current.isAfter(expireTime)) { logger.error("์ด๋ฏธ ๋งŒ๋ฃŒ๋œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ฐœ๊ฒฌ"); throw new RefreshTokenException(ErrorCode.RELOGIN_EXCEPTION); } // ์ƒˆ๋กœ ๋ฐœ๊ธ‰ํ•  accessToken๋ณด๋‹ค refreshToken์ด ๋จผ์ € ๋งŒ๋ฃŒ ๋  ๊ฒฝ์šฐ์ธ๊ฐ€? - if(expireTime.isAfter(expireDeadLine)) { + if (expireTime.isAfter(expireDeadLine)) { logger.info("๊ธฐ์กด ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ฐœ๊ธ‰"); return findRefreshToken; - } - else { + } else { logger.info("accessToken๋ณด๋‹ค ๋จผ์ € ๋งŒ๋ฃŒ๋  ์˜ˆ์ •์ธ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ฐœ๊ฒฌ"); deleteRefreshToken(request.getRefreshToken()); return generateRefreshToken(member.getSocialId(), member.getSocialType()); diff --git a/src/main/java/briefing/root/api/RootApi.java b/src/main/java/briefing/root/api/RootApi.java index 18c6354..6b70938 100644 --- a/src/main/java/briefing/root/api/RootApi.java +++ b/src/main/java/briefing/root/api/RootApi.java @@ -1,23 +1,22 @@ package briefing.root.api; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "01-Root \uD83C\uDF10",description = "๋ฃจํŠธ API") +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "01-Root \uD83C\uDF10", description = "๋ฃจํŠธ API") @RestController @RequestMapping("/") @RequiredArgsConstructor - public class RootApi { @Operation(summary = "01-01 Root\uD83C\uDF10 ํ—ฌ์Šค ์ฒดํฌ #FRAME", description = "ํ—ฌ์Šค ์ฒดํฌ API์ž…๋‹ˆ๋‹ค.") @GetMapping("/") - public String healthCheck(){ + public String healthCheck() { return "I'm healthy!!!!"; } } - diff --git a/src/main/java/briefing/scrap/api/ScrapApi.java b/src/main/java/briefing/scrap/api/ScrapApi.java index 3f91143..8c10535 100644 --- a/src/main/java/briefing/scrap/api/ScrapApi.java +++ b/src/main/java/briefing/scrap/api/ScrapApi.java @@ -1,5 +1,9 @@ package briefing.scrap.api; +import java.util.List; + +import org.springframework.web.bind.annotation.*; + import briefing.common.response.CommonResponse; import briefing.scrap.application.ScrapCommandService; import briefing.scrap.application.ScrapQueryService; @@ -9,9 +13,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import java.util.List; @Tag(name = "05-Scrap ๐Ÿ“", description = "์Šคํฌ๋žฉ ๊ด€๋ จ API") @RestController @@ -22,43 +23,48 @@ public class ScrapApi { @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V1", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/scraps/briefings") - public CommonResponse create(@RequestBody ScrapRequest.CreateDTO request) { + public CommonResponse create( + @RequestBody ScrapRequest.CreateDTO request) { Scrap createdScrap = scrapCommandService.create(request); return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); } @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/v2/scraps/briefings") - public CommonResponse createV2(@RequestBody ScrapRequest.CreateDTO request) { + public CommonResponse createV2( + @RequestBody ScrapRequest.CreateDTO request) { Scrap createdScrap = scrapCommandService.create(request); return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); } @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V1", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") - public CommonResponse delete(@PathVariable Long briefingId, @PathVariable Long memberId) { + public CommonResponse delete( + @PathVariable Long briefingId, @PathVariable Long memberId) { Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); } @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/v2/scraps/briefings/{briefingId}/members/{memberId}") - public CommonResponse deleteV2(@PathVariable Long briefingId, @PathVariable Long memberId) { + public CommonResponse deleteV2( + @PathVariable Long briefingId, @PathVariable Long memberId) { Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); } - @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V1", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/scraps/briefings/members/{memberId}") - public CommonResponse> getScrapsByMember(@PathVariable Long memberId) { + public CommonResponse> getScrapsByMember( + @PathVariable Long memberId) { List scraps = scrapQueryService.getScrapsByMemberId(memberId); return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTO).toList()); } @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/v2/scraps/briefings/members/{memberId}") - public CommonResponse> getScrapsByMemberV2(@PathVariable Long memberId) { + public CommonResponse> getScrapsByMemberV2( + @PathVariable Long memberId) { List scraps = scrapQueryService.getScrapsByMemberId(memberId); return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTOV2).toList()); } diff --git a/src/main/java/briefing/scrap/api/ScrapConverter.java b/src/main/java/briefing/scrap/api/ScrapConverter.java index d55d031..a603a2d 100644 --- a/src/main/java/briefing/scrap/api/ScrapConverter.java +++ b/src/main/java/briefing/scrap/api/ScrapConverter.java @@ -1,12 +1,12 @@ package briefing.scrap.api; +import java.time.LocalDateTime; + import briefing.briefing.domain.Briefing; import briefing.member.domain.Member; import briefing.scrap.application.dto.ScrapResponse; import briefing.scrap.domain.Scrap; -import java.time.LocalDateTime; - public class ScrapConverter { public static ScrapResponse.CreateDTO toCreateDTO(Scrap createdScrap) { return ScrapResponse.CreateDTO.builder() @@ -18,10 +18,7 @@ public static ScrapResponse.CreateDTO toCreateDTO(Scrap createdScrap) { } public static Scrap toScrap(Member member, Briefing briefing) { - return Scrap.builder() - .member(member) - .briefing(briefing) - .build(); + return Scrap.builder().member(member).briefing(briefing).build(); } public static ScrapResponse.DeleteDTO toDeleteDTO(Scrap deletedScrap) { diff --git a/src/main/java/briefing/scrap/application/ScrapCommandService.java b/src/main/java/briefing/scrap/application/ScrapCommandService.java index 49e0849..9dff2ed 100644 --- a/src/main/java/briefing/scrap/application/ScrapCommandService.java +++ b/src/main/java/briefing/scrap/application/ScrapCommandService.java @@ -1,5 +1,9 @@ package briefing.scrap.application; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import briefing.briefing.domain.Briefing; import briefing.briefing.domain.repository.BriefingRepository; import briefing.exception.ErrorCode; @@ -13,9 +17,6 @@ import briefing.scrap.domain.repository.ScrapRepository; import briefing.scrap.exception.ScrapException; import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @Transactional @@ -26,17 +27,21 @@ public class ScrapCommandService { private final MemberRepository memberRepository; private final BriefingRepository briefingRepository; - public Scrap create(ScrapRequest.CreateDTO request) { // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ - if(scrapRepository.existsByMember_IdAndBriefing_Id(request.getMemberId(), request.getBriefingId())) + if (scrapRepository.existsByMember_IdAndBriefing_Id( + request.getMemberId(), request.getBriefingId())) throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); - Member member = memberRepository.findById(request.getMemberId()) - .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + Member member = + memberRepository + .findById(request.getMemberId()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); - Briefing briefing = briefingRepository.findById(request.getBriefingId()) - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + Briefing briefing = + briefingRepository + .findById(request.getBriefingId()) + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); Scrap scrap = ScrapConverter.toScrap(member, briefing); @@ -51,8 +56,10 @@ public Scrap create(ScrapRequest.CreateDTO request) { } public Scrap delete(Long briefingId, Long memberId) { - Scrap scrap = scrapRepository.findByBriefing_IdAndMember_Id(briefingId, memberId) - .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); + Scrap scrap = + scrapRepository + .findByBriefing_IdAndMember_Id(briefingId, memberId) + .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); scrapRepository.delete(scrap); return scrap; } diff --git a/src/main/java/briefing/scrap/application/ScrapQueryService.java b/src/main/java/briefing/scrap/application/ScrapQueryService.java index 859c1d5..6515f54 100644 --- a/src/main/java/briefing/scrap/application/ScrapQueryService.java +++ b/src/main/java/briefing/scrap/application/ScrapQueryService.java @@ -1,12 +1,13 @@ package briefing.scrap.application; -import briefing.scrap.domain.Scrap; -import briefing.scrap.domain.repository.ScrapRepository; -import lombok.RequiredArgsConstructor; +import java.util.List; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; +import briefing.scrap.domain.Scrap; +import briefing.scrap.domain.repository.ScrapRepository; +import lombok.RequiredArgsConstructor; @Service @Transactional(readOnly = true) @@ -19,7 +20,6 @@ public List getScrapsByMemberId(Long memberId) { return scrapRepository.findByMember_Id(memberId); } - public Boolean existsByMemberIdAndBriefingId(Long memberId, Long briefingId) { return scrapRepository.existsByMember_IdAndBriefing_Id(memberId, briefingId); } diff --git a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java index 2b4a0ec..cc5ba42 100644 --- a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java +++ b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java @@ -1,16 +1,15 @@ package briefing.scrap.application.dto; +import java.time.LocalDate; +import java.time.LocalDateTime; + import briefing.briefing.domain.TimeOfDay; import briefing.chatting.domain.GptModel; -import jakarta.persistence.Column; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; -import java.time.LocalDateTime; - public class ScrapResponse { @Builder @@ -55,9 +54,7 @@ public static class ReadDTOV2 { private String title; private String subtitle; private LocalDate date; - @Builder.Default - private GptModel gptModel = GptModel.GPT_3_5_TURBO; - @Builder.Default - private TimeOfDay timeOfDay = TimeOfDay.MORNING; + @Builder.Default private GptModel gptModel = GptModel.GPT_3_5_TURBO; + @Builder.Default private TimeOfDay timeOfDay = TimeOfDay.MORNING; } } diff --git a/src/main/java/briefing/scrap/domain/Scrap.java b/src/main/java/briefing/scrap/domain/Scrap.java index 08d3a9d..6087f2b 100644 --- a/src/main/java/briefing/scrap/domain/Scrap.java +++ b/src/main/java/briefing/scrap/domain/Scrap.java @@ -1,18 +1,18 @@ package briefing.scrap.domain; +import jakarta.persistence.*; + import briefing.base.BaseDateTimeEntity; import briefing.briefing.domain.Briefing; import briefing.member.domain.Member; -import jakarta.persistence.*; import lombok.*; @Entity -@Getter @Builder +@Getter +@Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -@Table(uniqueConstraints = { - @UniqueConstraint(columnNames = {"member_id", "briefing_id"}) -}) +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"member_id", "briefing_id"})}) public class Scrap extends BaseDateTimeEntity { @Id @@ -27,9 +27,8 @@ public class Scrap extends BaseDateTimeEntity { @JoinColumn(nullable = false) private Briefing briefing; - public void setMember(Member member){ - if (this.member != null) - this.member.getScrapList().remove(this); + public void setMember(Member member) { + if (this.member != null) this.member.getScrapList().remove(this); this.member = member; member.getScrapList().add(this); } diff --git a/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java b/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java index 9acdad4..45bf2cd 100644 --- a/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java +++ b/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java @@ -1,11 +1,12 @@ package briefing.scrap.domain.repository; -import briefing.scrap.domain.Scrap; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +import briefing.scrap.domain.Scrap; + public interface ScrapRepository extends JpaRepository { Optional findByBriefing_IdAndMember_Id(Long briefingId, Long memberId); diff --git a/src/main/java/briefing/scrap/exception/ScrapException.java b/src/main/java/briefing/scrap/exception/ScrapException.java index 8b965ba..4a2f9f3 100644 --- a/src/main/java/briefing/scrap/exception/ScrapException.java +++ b/src/main/java/briefing/scrap/exception/ScrapException.java @@ -4,7 +4,7 @@ import briefing.exception.GeneralException; public class ScrapException extends GeneralException { - public ScrapException(ErrorCode errorCode){ + public ScrapException(ErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java index d16a5b3..31629d6 100644 --- a/src/main/java/briefing/security/config/SecurityConfig.java +++ b/src/main/java/briefing/security/config/SecurityConfig.java @@ -1,35 +1,32 @@ package briefing.security.config; -import briefing.security.filter.JwtRequestFilter; -import briefing.security.handler.JwtAccessDeniedHandler; -import briefing.security.handler.JwtAuthenticationEntryPoint; -import briefing.security.handler.JwtAuthenticationExceptionHandler; -import briefing.security.provider.TokenProvider; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import static org.springframework.security.config.Customizer.withDefaults; + +import java.util.Collections; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import java.util.Collections; - -import static org.springframework.security.config.Customizer.withDefaults; +import briefing.security.filter.JwtRequestFilter; +import briefing.security.handler.JwtAccessDeniedHandler; +import briefing.security.handler.JwtAuthenticationEntryPoint; +import briefing.security.handler.JwtAuthenticationExceptionHandler; +import briefing.security.provider.TokenProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Slf4j @EnableWebSecurity @@ -37,17 +34,17 @@ @Configuration public class SecurityConfig { - private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint = new JwtAuthenticationEntryPoint(); + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint = + new JwtAuthenticationEntryPoint(); private final JwtAccessDeniedHandler jwtAccessDeniedHandler = new JwtAccessDeniedHandler(); private final TokenProvider tokenProvider; - private final JwtAuthenticationExceptionHandler jwtAuthenticationExceptionHandler = new JwtAuthenticationExceptionHandler(); - - private static final String[] WHITE_LIST = { + private final JwtAuthenticationExceptionHandler jwtAuthenticationExceptionHandler = + new JwtAuthenticationExceptionHandler(); - }; + private static final String[] WHITE_LIST = {}; @Bean public PasswordEncoder passwordEncoder() { @@ -61,44 +58,59 @@ public RoleHierarchy roleHierarchy() { @Bean public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers( - "", - "/", - "/schedule", - "/swagger-ui.html", - "/v3/api-docs", - "/v3/api-docs/**", - "/swagger-ui/index.html", - "/swagger-ui/**", - "/docs/**","/briefings/temp"); + return (web) -> + web.ignoring() + .requestMatchers( + "", + "/", + "/schedule", + "/swagger-ui.html", + "/v3/api-docs", + "/v3/api-docs/**", + "/swagger-ui/index.html", + "/swagger-ui/**", + "/docs/**", + "/briefings/temp"); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http - .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfiguration())) + return http.cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfiguration())) .httpBasic(withDefaults()) .csrf(AbstractHttpConfigurer::disable) // ๋น„ํ™œ์„ฑํ™” - .sessionManagement(manage -> manage.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Session ์‚ฌ์šฉ ์•ˆํ•จ - .formLogin(AbstractHttpConfigurer::disable) // form login ์‚ฌ์šฉ ์•ˆํ•จ - .authorizeHttpRequests(authorize -> { - authorize.requestMatchers("/v2/briefings/**").permitAll(); // ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - authorize.requestMatchers("/briefings/**").permitAll(); // ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - authorize.requestMatchers("/v2/members/auth/**").permitAll(); - authorize.requestMatchers("/members/auth/**").permitAll(); - authorize.requestMatchers("/chattings/**").permitAll(); - authorize.requestMatchers(HttpMethod.DELETE, "/v2/members/{memberId}").authenticated(); - authorize.requestMatchers(HttpMethod.DELETE, "/members/{memberId}").authenticated(); - authorize.requestMatchers("/v2/scraps/**").authenticated(); - authorize.requestMatchers("/scraps/**").authenticated(); - authorize.anyRequest().authenticated(); - }) - .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(jwtAuthenticationEntryPoint) - .accessDeniedHandler(jwtAccessDeniedHandler) - ) - .addFilterBefore(new JwtRequestFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(jwtAuthenticationExceptionHandler,JwtRequestFilter.class) + .sessionManagement( + manage -> + manage.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) // Session ์‚ฌ์šฉ ์•ˆํ•จ + .formLogin(AbstractHttpConfigurer::disable) // form login ์‚ฌ์šฉ ์•ˆํ•จ + .authorizeHttpRequests( + authorize -> { + authorize + .requestMatchers("/v2/briefings/**") + .permitAll(); // ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + authorize.requestMatchers("/briefings/**").permitAll(); // ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + authorize.requestMatchers("/v2/members/auth/**").permitAll(); + authorize.requestMatchers("/members/auth/**").permitAll(); + authorize.requestMatchers("/chattings/**").permitAll(); + authorize + .requestMatchers(HttpMethod.DELETE, "/v2/members/{memberId}") + .authenticated(); + authorize + .requestMatchers(HttpMethod.DELETE, "/members/{memberId}") + .authenticated(); + authorize.requestMatchers("/v2/scraps/**").authenticated(); + authorize.requestMatchers("/scraps/**").authenticated(); + authorize.anyRequest().authenticated(); + }) + .exceptionHandling( + exceptionHandling -> + exceptionHandling + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .accessDeniedHandler(jwtAccessDeniedHandler)) + .addFilterBefore( + new JwtRequestFilter(tokenProvider), + UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtAuthenticationExceptionHandler, JwtRequestFilter.class) .build(); } diff --git a/src/main/java/briefing/security/filter/JwtRequestFilter.java b/src/main/java/briefing/security/filter/JwtRequestFilter.java index 690c2b8..73d712f 100644 --- a/src/main/java/briefing/security/filter/JwtRequestFilter.java +++ b/src/main/java/briefing/security/filter/JwtRequestFilter.java @@ -1,40 +1,41 @@ package briefing.security.filter; -import briefing.exception.ErrorCode; -import briefing.exception.handler.JwtAuthenticationException; -import briefing.security.provider.TokenProvider; +import java.io.IOException; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; + import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; +import briefing.security.provider.TokenProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor @Slf4j public class JwtRequestFilter extends OncePerRequestFilter { private final TokenProvider tokenProvider; - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { HttpServletRequest httpServletRequest = request; String jwt = tokenProvider.resolveToken(httpServletRequest); - if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)){ + if (StringUtils.hasText(jwt) + && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)) { Authentication authentication = tokenProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); - }else{ + } else { SecurityContextHolder.getContext().setAuthentication(null); } filterChain.doFilter(httpServletRequest, response); } - } diff --git a/src/main/java/briefing/security/handler/JwtAccessDeniedHandler.java b/src/main/java/briefing/security/handler/JwtAccessDeniedHandler.java index a027eff..533092b 100644 --- a/src/main/java/briefing/security/handler/JwtAccessDeniedHandler.java +++ b/src/main/java/briefing/security/handler/JwtAccessDeniedHandler.java @@ -1,48 +1,51 @@ package briefing.security.handler; -import briefing.exception.ErrorCode; -import briefing.exception.common.ApiErrorResult; +import java.io.IOException; +import java.io.PrintWriter; + import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.stereotype.Component; -import java.io.IOException; -import java.io.PrintWriter; +import briefing.exception.ErrorCode; +import briefing.exception.common.ApiErrorResult; public class JwtAccessDeniedHandler implements AccessDeniedHandler { private final Logger LOGGER = LoggerFactory.getLogger(JwtAccessDeniedHandler.class); @Override - public void handle(HttpServletRequest request, HttpServletResponse response, - AccessDeniedException accessDeniedException) throws IOException, ServletException { + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) + throws IOException, ServletException { response.setContentType("application/json; charset=UTF-8"); response.setStatus(403); PrintWriter writer = response.getWriter(); - ApiErrorResult apiErrorResult = ApiErrorResult.builder() - .isSuccess(false) - .code(ErrorCode._FORBIDDEN.getCode()) - .message(ErrorCode._FORBIDDEN.getMessage()) - .result(null) - .build(); - try{ + ApiErrorResult apiErrorResult = + ApiErrorResult.builder() + .isSuccess(false) + .code(ErrorCode._FORBIDDEN.getCode()) + .message(ErrorCode._FORBIDDEN.getMessage()) + .result(null) + .build(); + try { writer.write(apiErrorResult.toString()); - }catch(NullPointerException e){ + } catch (NullPointerException e) { LOGGER.error("์‘๋‹ต ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ ์—๋Ÿฌ", e); - }finally{ - if(writer != null) { + } finally { + if (writer != null) { writer.flush(); writer.close(); } } - } } diff --git a/src/main/java/briefing/security/handler/JwtAuthenticationEntryPoint.java b/src/main/java/briefing/security/handler/JwtAuthenticationEntryPoint.java index 4ecf1ea..5da2d90 100644 --- a/src/main/java/briefing/security/handler/JwtAuthenticationEntryPoint.java +++ b/src/main/java/briefing/security/handler/JwtAuthenticationEntryPoint.java @@ -1,41 +1,46 @@ package briefing.security.handler; -import briefing.exception.ErrorCode; -import briefing.exception.common.ApiErrorResult; +import java.io.IOException; +import java.io.PrintWriter; + import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; -import java.io.IOException; -import java.io.PrintWriter; +import briefing.exception.ErrorCode; +import briefing.exception.common.ApiErrorResult; public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { private final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) + throws IOException, ServletException { response.setContentType("application/json; charset=UTF-8"); response.setStatus(401); PrintWriter writer = response.getWriter(); - ApiErrorResult apiErrorResult = ApiErrorResult.builder() - .isSuccess(false) - .code(ErrorCode._UNAUTHORIZED.getCode()) - .message(ErrorCode._UNAUTHORIZED.getMessage()) - .result(null) - .build(); + ApiErrorResult apiErrorResult = + ApiErrorResult.builder() + .isSuccess(false) + .code(ErrorCode._UNAUTHORIZED.getCode()) + .message(ErrorCode._UNAUTHORIZED.getMessage()) + .result(null) + .build(); try { writer.write(apiErrorResult.toString()); - }catch (NullPointerException e){ + } catch (NullPointerException e) { LOGGER.error("์‘๋‹ต ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ ์—๋Ÿฌ", e); - }finally { - if(writer != null){ + } finally { + if (writer != null) { writer.flush(); writer.close(); } diff --git a/src/main/java/briefing/security/handler/JwtAuthenticationExceptionHandler.java b/src/main/java/briefing/security/handler/JwtAuthenticationExceptionHandler.java index 5390818..b3e7878 100644 --- a/src/main/java/briefing/security/handler/JwtAuthenticationExceptionHandler.java +++ b/src/main/java/briefing/security/handler/JwtAuthenticationExceptionHandler.java @@ -1,26 +1,29 @@ package briefing.security.handler; -import briefing.exception.ErrorCode; -import briefing.exception.common.ApiErrorResult; -import briefing.exception.handler.JwtAuthenticationException; +import java.io.IOException; +import java.io.PrintWriter; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.io.PrintWriter; +import briefing.exception.ErrorCode; +import briefing.exception.common.ApiErrorResult; +import briefing.exception.handler.JwtAuthenticationException; -//@Component +// @Component public class JwtAuthenticationExceptionHandler extends OncePerRequestFilter { @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { try { - filterChain.doFilter(request,response); - }catch (JwtAuthenticationException authException){ + filterChain.doFilter(request, response); + } catch (JwtAuthenticationException authException) { response.setContentType("application/json; charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); @@ -28,12 +31,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String errorCodeName = authException.getMessage(); ErrorCode code = ErrorCode.valueOf(errorCodeName); - ApiErrorResult apiErrorResult = ApiErrorResult.builder() - .isSuccess(false) - .code(code.getCode()) - .message(code.getMessage()) - .result(null) - .build(); + ApiErrorResult apiErrorResult = + ApiErrorResult.builder() + .isSuccess(false) + .code(code.getCode()) + .message(code.getMessage()) + .result(null) + .build(); writer.write(apiErrorResult.toString()); writer.flush(); diff --git a/src/main/java/briefing/security/handler/annotation/AuthMember.java b/src/main/java/briefing/security/handler/annotation/AuthMember.java index 3992618..7178ca4 100644 --- a/src/main/java/briefing/security/handler/annotation/AuthMember.java +++ b/src/main/java/briefing/security/handler/annotation/AuthMember.java @@ -7,5 +7,4 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) -public @interface AuthMember { -} +public @interface AuthMember {} diff --git a/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java b/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java index fa20489..92aacfe 100644 --- a/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java +++ b/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java @@ -1,17 +1,8 @@ package briefing.security.handler.annotation; -import briefing.exception.ErrorCode; -import briefing.exception.handler.MemberException; -import briefing.member.api.MemberConverter; -import briefing.member.application.MemberQueryService; -import briefing.member.domain.Member; -import briefing.security.provider.TokenProvider; import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; + import org.springframework.core.MethodParameter; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -19,11 +10,15 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import briefing.member.application.MemberQueryService; +import briefing.member.domain.Member; +import briefing.security.provider.TokenProvider; +import lombok.RequiredArgsConstructor; + @Component @RequiredArgsConstructor public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver { - private final MemberQueryService memberQueryService; private final TokenProvider tokenProvider; @@ -37,7 +32,6 @@ public boolean supportsParameter(MethodParameter parameter) { return true; } - /* NOTE - ์‚ฌ์šฉ์ž ์ถ”์ถœ ๋ฐฉ๋ฒ• ๋ณ€๊ฒฝ ๊ธฐ์กด) ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ปจํ…์ŠคํŠธ์—์„œ ์œ ์ €์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ์„ธํŒ…ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. @@ -45,16 +39,22 @@ public boolean supportsParameter(MethodParameter parameter) { ๋ณ€๊ฒฝ ๋ฐฐ๊ฒฝ) permitAll()์„ ํ†ตํ•ด ์ธ์ฆ ์œ ๋ฌด์— ์ƒ๊ด€์—†์ด ์ œ๊ณต๋˜๋Š” API์—์„œ JwtRequestFilter๊นŒ์ง€ ๋„๋‹ฌํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ์ธํ•ด authentication ๊ฐ์ฒด๊ฐ€ ์„ธํŒ…์ด ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ํ† ํฐ์„ ๋ฌผ๊ณ  ์žˆ๋Š” ์‚ฌ์šฉ์ž๋ผ๋„ AuthMember๋ฅผ ํ†ตํ•ด ์ถ”์ถœํ•˜๋ฉด null์ด ๋ฐ˜ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. - */ + */ @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) + throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); String jwt = tokenProvider.resolveToken(request); - if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)) { + if (StringUtils.hasText(jwt) + && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)) { // ํ† ํฐ์—์„œ ์‚ฌ์šฉ์ž ID (subject) ์ถ”์ถœ String userId = tokenProvider.getAuthentication(jwt).getName(); return memberQueryService.findById(Long.valueOf(userId)); } - return null; // ํ† ํฐ์ด ์—†๊ฑฐ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ + return null; // ํ† ํฐ์ด ์—†๊ฑฐ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ } } diff --git a/src/main/java/briefing/security/provider/TokenProvider.java b/src/main/java/briefing/security/provider/TokenProvider.java index b0c5000..02570b8 100644 --- a/src/main/java/briefing/security/provider/TokenProvider.java +++ b/src/main/java/briefing/security/provider/TokenProvider.java @@ -1,10 +1,14 @@ package briefing.security.provider; -import briefing.exception.ErrorCode; -import briefing.exception.handler.JwtAuthenticationException; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; +import java.security.Key; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Date; +import java.util.stream.Collectors; + import jakarta.servlet.http.HttpServletRequest; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -17,12 +21,10 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import java.security.Key; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.Date; -import java.util.stream.Collectors; +import briefing.exception.ErrorCode; +import briefing.exception.handler.JwtAuthenticationException; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; @Component public class TokenProvider implements InitializingBean { @@ -37,21 +39,24 @@ public class TokenProvider implements InitializingBean { private final long accessTokenValidityInMilliseconds; -// private final RefreshTokenRepository refreshTokenRepository; + // private final RefreshTokenRepository refreshTokenRepository; private Key key; - public enum TokenType{ - ACCESS, REFRESH; + public enum TokenType { + ACCESS, + REFRESH; } - public TokenProvider(@Value("${jwt.secret}") String secret, - @Value("${jwt.authorities-key}") String authoritiesKey, - @Value("${jwt.access-token-validity-in-seconds}") long accessTokenValidityInMilliseconds){ + public TokenProvider( + @Value("${jwt.secret}") String secret, + @Value("${jwt.authorities-key}") String authoritiesKey, + @Value("${jwt.access-token-validity-in-seconds}") + long accessTokenValidityInMilliseconds) { this.secret = secret; this.AUTHORITIES_KEY = authoritiesKey; this.accessTokenValidityInMilliseconds = accessTokenValidityInMilliseconds; -// this.refreshTokenRepository = refreshTokenRepository; + // this.refreshTokenRepository = refreshTokenRepository; } @Override @@ -61,7 +66,11 @@ public void afterPropertiesSet() throws Exception { } // ์ˆ˜์ • ํ•ด์•ผํ•จ - public String createAccessToken(Long userId, String socialType, String socialId, Collection authorities){ + public String createAccessToken( + Long userId, + String socialType, + String socialId, + Collection authorities) { long now = (new Date()).getTime(); Date validity = new Date(now + this.accessTokenValidityInMilliseconds); @@ -75,7 +84,8 @@ public String createAccessToken(Long userId, String socialType, String socialId, .compact(); } - public String createAccessToken(Long userId,String phoneNum, Collection authorities){ + public String createAccessToken( + Long userId, String phoneNum, Collection authorities) { long now = (new Date()).getTime(); Date validity = new Date(now + this.accessTokenValidityInMilliseconds); @@ -88,12 +98,9 @@ public String createAccessToken(Long userId,String phoneNum, Collection authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) @@ -104,40 +111,41 @@ public Authentication getAuthentication(String token){ } public boolean validateToken(String token, TokenType type) throws JwtAuthenticationException { - try{ + try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; - }catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e){ + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { throw new JwtAuthenticationException(ErrorCode.INVALID_TOKEN_EXCEPTION); - }catch (ExpiredJwtException e){ - if (type == TokenType.ACCESS) throw new JwtAuthenticationException(ErrorCode.EXPIRED_JWT_EXCEPTION); + } catch (ExpiredJwtException e) { + if (type == TokenType.ACCESS) + throw new JwtAuthenticationException(ErrorCode.EXPIRED_JWT_EXCEPTION); else throw new JwtAuthenticationException(ErrorCode.RELOGIN_EXCEPTION); - }catch (UnsupportedJwtException e){ + } catch (UnsupportedJwtException e) { throw new JwtAuthenticationException(ErrorCode.INVALID_TOKEN_EXCEPTION); - }catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { throw new JwtAuthenticationException(ErrorCode.INVALID_TOKEN_EXCEPTION); } } -// public Long validateAndReturnId(String token) throws JwtAuthenticationException{ -// try{ -// Claims body = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); -// return Long.valueOf(body.getSubject()); -// }catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e){ -// throw new JwtAuthenticationException(Er.JWT_BAD_REQUEST); -// }catch (UnsupportedJwtException e){ -// throw new JwtAuthenticationException(Code.JWT_UNSUPPORTED_TOKEN); -// }catch (IllegalArgumentException e){ -// throw new JwtAuthenticationException(Code.JWT_BAD_REQUEST); -// } -// } - - public String resolveToken(HttpServletRequest request){ + // public Long validateAndReturnId(String token) throws JwtAuthenticationException{ + // try{ + // Claims body = + // Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); + // return Long.valueOf(body.getSubject()); + // }catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e){ + // throw new JwtAuthenticationException(Er.JWT_BAD_REQUEST); + // }catch (UnsupportedJwtException e){ + // throw new JwtAuthenticationException(Code.JWT_UNSUPPORTED_TOKEN); + // }catch (IllegalArgumentException e){ + // throw new JwtAuthenticationException(Code.JWT_BAD_REQUEST); + // } + // } + + public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(AUTHORIZATION_HEADER); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){ + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } - } diff --git a/src/main/java/briefing/validation/annotation/CheckSameMember.java b/src/main/java/briefing/validation/annotation/CheckSameMember.java index be4aac7..14c2182 100644 --- a/src/main/java/briefing/validation/annotation/CheckSameMember.java +++ b/src/main/java/briefing/validation/annotation/CheckSameMember.java @@ -1,17 +1,20 @@ package briefing.validation.annotation; -import briefing.validation.validator.CheckSameMemberValidator; +import java.lang.annotation.*; + import jakarta.validation.Constraint; import jakarta.validation.Payload; -import java.lang.annotation.*; +import briefing.validation.validator.CheckSameMemberValidator; @Documented @Constraint(validatedBy = CheckSameMemberValidator.class) -@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckSameMember { String message() default "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ๋Œ€์ƒ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."; + Class[] groups() default {}; + Class[] payload() default {}; } diff --git a/src/main/java/briefing/validation/validator/CheckSameMemberValidator.java b/src/main/java/briefing/validation/validator/CheckSameMemberValidator.java index a850561..8a81240 100644 --- a/src/main/java/briefing/validation/validator/CheckSameMemberValidator.java +++ b/src/main/java/briefing/validation/validator/CheckSameMemberValidator.java @@ -1,24 +1,20 @@ package briefing.validation.validator; -import briefing.exception.ErrorCode; -import briefing.exception.handler.MemberException; -import briefing.member.application.MemberQueryService; -import briefing.member.domain.Member; -import briefing.validation.annotation.CheckSameMember; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; -import lombok.RequiredArgsConstructor; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import java.util.concurrent.RecursiveTask; +import briefing.exception.ErrorCode; +import briefing.validation.annotation.CheckSameMember; +import lombok.RequiredArgsConstructor; @Component @RequiredArgsConstructor -public class CheckSameMemberValidator implements ConstraintValidator{ - +public class CheckSameMemberValidator implements ConstraintValidator { @Override public void initialize(CheckSameMember constraintAnnotation) { @@ -35,15 +31,18 @@ public boolean isValid(Long value, ConstraintValidatorContext context) { } if (principal == null || principal.getClass() == String.class) { context.disableDefaultConstraintViolation(); - context.buildConstraintViolationWithTemplate(ErrorCode.MEMBER_NOT_FOUND.toString()).addConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorCode.MEMBER_NOT_FOUND.toString()) + .addConstraintViolation(); return false; } - UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication; + UsernamePasswordAuthenticationToken authenticationToken = + (UsernamePasswordAuthenticationToken) authentication; // ๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋“œ๋ฏผ์ธ์ง€ ๋‚˜์ค‘์— ์ถ”๊ฐ€ - if(!value.equals(Long.valueOf(authenticationToken.getName()))){ + if (!value.equals(Long.valueOf(authenticationToken.getName()))) { context.disableDefaultConstraintViolation(); - context.buildConstraintViolationWithTemplate(ErrorCode.MEMBER_NOT_SAME.toString()).addConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorCode.MEMBER_NOT_SAME.toString()) + .addConstraintViolation(); return false; } return true; diff --git a/src/test/java/briefing/BriefingApplicationTests.java b/src/test/java/briefing/BriefingApplicationTests.java index 422a5d4..e37ab02 100644 --- a/src/test/java/briefing/BriefingApplicationTests.java +++ b/src/test/java/briefing/BriefingApplicationTests.java @@ -6,8 +6,6 @@ @SpringBootTest class BriefingApplicationTests { - @Test - void contextLoads() { - } - + @Test + void contextLoads() {} } diff --git a/src/test/java/briefing/briefing/application/BriefingQueryServiceTest.java b/src/test/java/briefing/briefing/application/BriefingQueryServiceTest.java index 08b1ae4..ff0dba9 100644 --- a/src/test/java/briefing/briefing/application/BriefingQueryServiceTest.java +++ b/src/test/java/briefing/briefing/application/BriefingQueryServiceTest.java @@ -4,16 +4,10 @@ import static briefing.briefing.domain.BriefingType.KOREA; import static org.assertj.core.api.Assertions.assertThat; -import briefing.briefing.domain.Article; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.BriefingArticle; -import briefing.briefing.domain.BriefingType; -import briefing.briefing.domain.repository.ArticleRepository; -import briefing.briefing.domain.repository.BriefingArticleRepository; -import briefing.briefing.domain.repository.BriefingRepository; import java.time.LocalDate; import java.util.Collections; import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -24,104 +18,108 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import briefing.briefing.domain.Article; +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.BriefingArticle; +import briefing.briefing.domain.BriefingType; +import briefing.briefing.domain.repository.ArticleRepository; +import briefing.briefing.domain.repository.BriefingArticleRepository; +import briefing.briefing.domain.repository.BriefingRepository; + @SpringBootTest @Sql(value = "/init.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) class BriefingQueryServiceTest { - @Autowired - private BriefingQueryService briefingQueryService; - @Autowired - private BriefingRepository briefingRepository; - @Autowired - private ArticleRepository articleRepository; - @Autowired - private BriefingArticleRepository briefingArticleRepository; - - private LocalDate date; - private List koreaBriefings; - private List globalBriefings; - private List
articles; - private List briefingArticles; - private Briefing briefing; - - @BeforeEach - void setUp() { - date = LocalDate.now(); - - koreaBriefings = briefingRepository.saveAll(List.of( - new Briefing(KOREA, 1, "์ œ๋ชฉ1", "๋ถ€์ œ๋ชฉ1", "๋‚ด์šฉ1"), - new Briefing(KOREA, 2, "์ œ๋ชฉ2", "๋ถ€์ œ๋ชฉ2", "๋‚ด์šฉ2"), - new Briefing(KOREA, 3, "์ œ๋ชฉ3", "๋ถ€์ œ๋ชฉ3", "๋‚ด์šฉ3"), - new Briefing(KOREA, 4, "์ œ๋ชฉ4", "๋ถ€์ œ๋ชฉ4", "๋‚ด์šฉ4"), - new Briefing(KOREA, 5, "์ œ๋ชฉ5", "๋ถ€์ œ๋ชฉ5", "๋‚ด์šฉ5") - )); - globalBriefings = briefingRepository.saveAll(List.of( - new Briefing(GLOBAL, 1, "title1", "subtitle1", "content1"), - new Briefing(GLOBAL, 2, "title2", "subtitle2", "content2"), - new Briefing(GLOBAL, 3, "title3", "subtitle3", "content3"), - new Briefing(GLOBAL, 4, "title4", "subtitle4", "content4"), - new Briefing(GLOBAL, 5, "title5", "subtitle5", "content5") - )); - - articles = articleRepository.saveAll(List.of( - new Article("์–ธ๋ก ์‚ฌ1", "๊ธฐ์‚ฌ1", "https://url.com"), - new Article("์–ธ๋ก ์‚ฌ2", "๊ธฐ์‚ฌ2", "https://url.com") - )); - - briefing = koreaBriefings.get(0); - briefingArticles = briefingArticleRepository.saveAll(articles.stream() - .map(article -> new BriefingArticle(briefing, article)) - .toList()); - briefing.getBriefingArticles().addAll(briefingArticles); - } - - @ParameterizedTest - @EnumSource(BriefingType.class) - @DisplayName("ํ•ด๋‹นํ•˜๋Š” type๊ณผ date์˜ ๋ธŒ๋ฆฌํ•‘ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") - void findBriefingsTest(final BriefingType type) { - //given - final List briefings = type.equals(KOREA) ? koreaBriefings : globalBriefings; - final BriefingsResponse expect = BriefingsResponse.from(date, briefings); - - //when - final BriefingsResponse actual = briefingQueryService.findBriefings(type, date); - - //then - assertThat(actual) - .usingRecursiveComparison() - .isEqualTo(expect); - } - - @ParameterizedTest - @EnumSource(BriefingType.class) - @DisplayName("ํ•ด๋‹นํ•˜๋Š” date์˜ ๋ธŒ๋ฆฌํ•‘์ด ์—†๋‹ค๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") - void findBriefingsWithNotExistDateTest(final BriefingType type) { - //given - final LocalDate notExistDate = date.plusDays(1); - final BriefingsResponse expect = BriefingsResponse.from(notExistDate, Collections.emptyList()); - - //when - final BriefingsResponse actual = briefingQueryService.findBriefings(type, notExistDate); - - //then - assertThat(actual) - .usingRecursiveComparison() - .isEqualTo(expect); - } - - @Test - @DisplayName("๋ธŒ๋ฆฌํ•‘์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") - void findBriefingTest() { - //given - final Long briefingId = briefing.getId(); - final BriefingDetailResponse expect = BriefingDetailResponse.from(briefing); - - //when - final BriefingDetailResponse actual = briefingQueryService.findBriefing(briefingId); - - //then - assertThat(actual) - .usingRecursiveComparison() - .isEqualTo(expect); - } + @Autowired private BriefingQueryService briefingQueryService; + @Autowired private BriefingRepository briefingRepository; + @Autowired private ArticleRepository articleRepository; + @Autowired private BriefingArticleRepository briefingArticleRepository; + + private LocalDate date; + private List koreaBriefings; + private List globalBriefings; + private List
articles; + private List briefingArticles; + private Briefing briefing; + + @BeforeEach + void setUp() { + date = LocalDate.now(); + + koreaBriefings = + briefingRepository.saveAll( + List.of( + new Briefing(KOREA, 1, "์ œ๋ชฉ1", "๋ถ€์ œ๋ชฉ1", "๋‚ด์šฉ1"), + new Briefing(KOREA, 2, "์ œ๋ชฉ2", "๋ถ€์ œ๋ชฉ2", "๋‚ด์šฉ2"), + new Briefing(KOREA, 3, "์ œ๋ชฉ3", "๋ถ€์ œ๋ชฉ3", "๋‚ด์šฉ3"), + new Briefing(KOREA, 4, "์ œ๋ชฉ4", "๋ถ€์ œ๋ชฉ4", "๋‚ด์šฉ4"), + new Briefing(KOREA, 5, "์ œ๋ชฉ5", "๋ถ€์ œ๋ชฉ5", "๋‚ด์šฉ5"))); + globalBriefings = + briefingRepository.saveAll( + List.of( + new Briefing(GLOBAL, 1, "title1", "subtitle1", "content1"), + new Briefing(GLOBAL, 2, "title2", "subtitle2", "content2"), + new Briefing(GLOBAL, 3, "title3", "subtitle3", "content3"), + new Briefing(GLOBAL, 4, "title4", "subtitle4", "content4"), + new Briefing(GLOBAL, 5, "title5", "subtitle5", "content5"))); + + articles = + articleRepository.saveAll( + List.of( + new Article("์–ธ๋ก ์‚ฌ1", "๊ธฐ์‚ฌ1", "https://url.com"), + new Article("์–ธ๋ก ์‚ฌ2", "๊ธฐ์‚ฌ2", "https://url.com"))); + + briefing = koreaBriefings.get(0); + briefingArticles = + briefingArticleRepository.saveAll( + articles.stream() + .map(article -> new BriefingArticle(briefing, article)) + .toList()); + briefing.getBriefingArticles().addAll(briefingArticles); + } + + @ParameterizedTest + @EnumSource(BriefingType.class) + @DisplayName("ํ•ด๋‹นํ•˜๋Š” type๊ณผ date์˜ ๋ธŒ๋ฆฌํ•‘ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") + void findBriefingsTest(final BriefingType type) { + // given + final List briefings = type.equals(KOREA) ? koreaBriefings : globalBriefings; + final BriefingsResponse expect = BriefingsResponse.from(date, briefings); + + // when + final BriefingsResponse actual = briefingQueryService.findBriefings(type, date); + + // then + assertThat(actual).usingRecursiveComparison().isEqualTo(expect); + } + + @ParameterizedTest + @EnumSource(BriefingType.class) + @DisplayName("ํ•ด๋‹นํ•˜๋Š” date์˜ ๋ธŒ๋ฆฌํ•‘์ด ์—†๋‹ค๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") + void findBriefingsWithNotExistDateTest(final BriefingType type) { + // given + final LocalDate notExistDate = date.plusDays(1); + final BriefingsResponse expect = + BriefingsResponse.from(notExistDate, Collections.emptyList()); + + // when + final BriefingsResponse actual = briefingQueryService.findBriefings(type, notExistDate); + + // then + assertThat(actual).usingRecursiveComparison().isEqualTo(expect); + } + + @Test + @DisplayName("๋ธŒ๋ฆฌํ•‘์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") + void findBriefingTest() { + // given + final Long briefingId = briefing.getId(); + final BriefingDetailResponse expect = BriefingDetailResponse.from(briefing); + + // when + final BriefingDetailResponse actual = briefingQueryService.findBriefing(briefingId); + + // then + assertThat(actual).usingRecursiveComparison().isEqualTo(expect); + } } diff --git a/src/test/java/briefing/chatting/application/ChattingCommandServiceTest.java b/src/test/java/briefing/chatting/application/ChattingCommandServiceTest.java index 03d2219..c757698 100644 --- a/src/test/java/briefing/chatting/application/ChattingCommandServiceTest.java +++ b/src/test/java/briefing/chatting/application/ChattingCommandServiceTest.java @@ -11,15 +11,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import briefing.chatting.domain.Chatting; -import briefing.chatting.domain.Message; -import briefing.chatting.domain.MessageRole; -import briefing.chatting.domain.repository.ChattingRepository; -import briefing.chatting.domain.repository.MessageRepository; -import briefing.chatting.exception.ChattingException; -import briefing.chatting.exception.ChattingExceptionType; import java.util.ArrayList; import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -34,166 +28,174 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import briefing.chatting.domain.Chatting; +import briefing.chatting.domain.Message; +import briefing.chatting.domain.MessageRole; +import briefing.chatting.domain.repository.ChattingRepository; +import briefing.chatting.domain.repository.MessageRepository; +import briefing.chatting.exception.ChattingException; +import briefing.chatting.exception.ChattingExceptionType; + @SpringBootTest @Sql(value = "/init.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) class ChattingCommandServiceTest { - @Autowired - private ChattingCommandService chattingCommandService; - @Autowired - private ChattingRepository chattingRepository; - @Autowired - private MessageRepository messageRepository; - - @MockBean - private ChatGptClient chatGptClient; - - @Test - void createChattingTest() { - //given - final ChattingCreateResponse expect = chattingCommandService.createChatting(); - - //when - final ChattingCreateResponse actual = ChattingCreateResponse.from( - chattingRepository.findById(expect.id()).get() - ); - - //then - assertNotNull(actual); - assertEquals(expect.id(), actual.id()); - } - - @Nested - @DisplayName("์งˆ๋ฌธ ์ €์žฅ ๋ฐ ๋Œ€๋‹ต ์š”์ฒญ ํ…Œ์ŠคํŠธ") - class RequestAnswer { - - private long chattingId; - private Chatting chatting; - private AnswerRequest request; - private List messages; - - @BeforeEach - void setUp() { - chatting = chattingRepository.save(new Chatting()); - chattingId = chatting.getId(); - - messages = List.of( - new MessageRequest( - SYSTEM, - "You are News briefier. User will ask you some questions, then you have to say the answer with given context. You only can say truth, and only can say korean. thsi is given context" - ), - new MessageRequest( - ASSISTANT, - "Brief๋Š” ์–ด์ œ์˜ ์ด์Šˆ์— ๋Œ€ํ•ด์„œ ๋‰ด์Šค ๋“ฑ์˜ ๊ธฐ์‚ฌ๋ฅผ ํ†ตํ•ด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋‚ด์šฉ์€ 100% ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ๋‚ด์šฉ์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋†’์€ ์‹ ๋ขฐ๋„๋ฅผ ์œ„ํ•ด์„œ๋Š” ์ถ”์ฒœ ๊ธฐ์‚ฌ ๋“ฑ์„ ํ†ตํ•ด ์ •๋ณด๋ฅผ ํ™•์ธํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒƒ์ด ๊ถ๊ธˆํ•˜์‹ ๊ฐ€์š”?" - ), - new MessageRequest( - USER, - "์žผ๋ฒ„๋ฆฌ๊ฐ€ ๋ญ์•ผ?" - ) - ); - request = new AnswerRequest(GPT_3_5_TURBO, messages); - } + @Autowired private ChattingCommandService chattingCommandService; + @Autowired private ChattingRepository chattingRepository; + @Autowired private MessageRepository messageRepository; - @Test - @DisplayName("์ฑ„ํŒ…์˜ ์งˆ๋ฌธ๊ณผ ๋Œ€๋‹ต์„ ์ •์ƒ์ ์œผ๋กœ ์ €์žฅํ•œ๋‹ค.") - void requestAnswerTest() { - //given - final MessageRole expectRole = ASSISTANT; - final String expectContent = "์˜ˆ์ƒ ๋Œ€๋‹ต"; - - when(chatGptClient.requestAnswer(any(), any())) - .thenReturn(new Message(chatting, expectRole, expectContent)); - - //when - final AnswerResponse expect = chattingCommandService.requestAnswer(chattingId, request); - final AnswerResponse actual = AnswerResponse.from( - messageRepository.findById(expect.id()).get()); - - //then - assertAll( - () -> assertEquals(expect.id(), actual.id()), - () -> assertEquals(expect.role(), actual.role()), - () -> assertEquals(expect.content(), actual.content()) - ); - } + @MockBean private ChatGptClient chatGptClient; @Test - @DisplayName("์ฑ„ํŒ…์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ NOT_FOUND_CHATTING ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") - void requestAnswerWithNotExistChattingTest() { - //given - final long notExistChattingId = 0L; - final ChattingExceptionType expect = ChattingExceptionType.NOT_FOUND_CHATTING; - - //when - final ChattingException actualException = assertThrowsExactly( - ChattingException.class, - () -> chattingCommandService.requestAnswer(notExistChattingId, request) - ); - - //then - assertEquals(expect, actualException.exceptionType()); - } + void createChattingTest() { + // given + final ChattingCreateResponse expect = chattingCommandService.createChatting(); - @Test - @DisplayName("messages๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ LAST_MESSAGE_NOT_EXIST ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") - void requestAnswerWithEmptyMessagesTest() { - //given - final List emptyMessages = new ArrayList<>(); - final AnswerRequest emptyMessageRequest = new AnswerRequest(GPT_3_5_TURBO, emptyMessages); - - final ChattingExceptionType expect = ChattingExceptionType.LAST_MESSAGE_NOT_EXIST; - - //when - final ChattingException actualException = assertThrowsExactly( - ChattingException.class, - () -> chattingCommandService.requestAnswer(chattingId, emptyMessageRequest) - ); - - //then - assertEquals(expect, actualException.exceptionType()); - } + // when + final ChattingCreateResponse actual = + ChattingCreateResponse.from(chattingRepository.findById(expect.id()).get()); - @ParameterizedTest - @EnumSource(value = MessageRole.class, names = {"SYSTEM", "ASSISTANT"}) - @DisplayName("๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€(์งˆ๋ฌธ)์˜ role์ด user๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ BAD_LAST_MESSAGE_ROLE ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") - void requestAnswerWithLastMessageRoleNotUserTest(final MessageRole role) { - //given - final List messages = new ArrayList<>(this.messages); - messages.add(new MessageRequest(role, "์ž˜๋ชป๋œ role์˜ ์งˆ๋ฌธ")); - final AnswerRequest wrongRequest = new AnswerRequest(GPT_3_5_TURBO, messages); - - final ChattingExceptionType expect = ChattingExceptionType.BAD_LAST_MESSAGE_ROLE; - - //when - final ChattingException actualException = assertThrowsExactly( - ChattingException.class, - () -> chattingCommandService.requestAnswer(chattingId, wrongRequest) - ); - - //then - assertEquals(expect, actualException.exceptionType()); + // then + assertNotNull(actual); + assertEquals(expect.id(), actual.id()); } - @ParameterizedTest - @NullSource - @ValueSource(strings = {" ", "\n", "\t"}) - @DisplayName("๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€(์งˆ๋ฌธ)์˜ content๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ CAN_NOT_EMPTY_CONTENT ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") - void requestAnswerWithEmptyContentTest(final String content) { - //given - final List messages = new ArrayList<>(this.messages); - messages.add(new MessageRequest(USER, content)); - final AnswerRequest emptyMessageRequest = new AnswerRequest(GPT_3_5_TURBO, messages); - - final ChattingExceptionType expect = ChattingExceptionType.CAN_NOT_EMPTY_CONTENT; - - //when - final ChattingException actualException = assertThrowsExactly( - ChattingException.class, - () -> chattingCommandService.requestAnswer(chattingId, emptyMessageRequest) - ); - - //then - assertEquals(expect, actualException.exceptionType()); + @Nested + @DisplayName("์งˆ๋ฌธ ์ €์žฅ ๋ฐ ๋Œ€๋‹ต ์š”์ฒญ ํ…Œ์ŠคํŠธ") + class RequestAnswer { + + private long chattingId; + private Chatting chatting; + private AnswerRequest request; + private List messages; + + @BeforeEach + void setUp() { + chatting = chattingRepository.save(new Chatting()); + chattingId = chatting.getId(); + + messages = + List.of( + new MessageRequest( + SYSTEM, + "You are News briefier. User will ask you some questions, then you have to say the answer with given context. You only can say truth, and only can say korean. thsi is given context"), + new MessageRequest( + ASSISTANT, + "Brief๋Š” ์–ด์ œ์˜ ์ด์Šˆ์— ๋Œ€ํ•ด์„œ ๋‰ด์Šค ๋“ฑ์˜ ๊ธฐ์‚ฌ๋ฅผ ํ†ตํ•ด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋‚ด์šฉ์€ 100% ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ๋‚ด์šฉ์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋†’์€ ์‹ ๋ขฐ๋„๋ฅผ ์œ„ํ•ด์„œ๋Š” ์ถ”์ฒœ ๊ธฐ์‚ฌ ๋“ฑ์„ ํ†ตํ•ด ์ •๋ณด๋ฅผ ํ™•์ธํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒƒ์ด ๊ถ๊ธˆํ•˜์‹ ๊ฐ€์š”?"), + new MessageRequest(USER, "์žผ๋ฒ„๋ฆฌ๊ฐ€ ๋ญ์•ผ?")); + request = new AnswerRequest(GPT_3_5_TURBO, messages); + } + + @Test + @DisplayName("์ฑ„ํŒ…์˜ ์งˆ๋ฌธ๊ณผ ๋Œ€๋‹ต์„ ์ •์ƒ์ ์œผ๋กœ ์ €์žฅํ•œ๋‹ค.") + void requestAnswerTest() { + // given + final MessageRole expectRole = ASSISTANT; + final String expectContent = "์˜ˆ์ƒ ๋Œ€๋‹ต"; + + when(chatGptClient.requestAnswer(any(), any())) + .thenReturn(new Message(chatting, expectRole, expectContent)); + + // when + final AnswerResponse expect = chattingCommandService.requestAnswer(chattingId, request); + final AnswerResponse actual = + AnswerResponse.from(messageRepository.findById(expect.id()).get()); + + // then + assertAll( + () -> assertEquals(expect.id(), actual.id()), + () -> assertEquals(expect.role(), actual.role()), + () -> assertEquals(expect.content(), actual.content())); + } + + @Test + @DisplayName("์ฑ„ํŒ…์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ NOT_FOUND_CHATTING ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") + void requestAnswerWithNotExistChattingTest() { + // given + final long notExistChattingId = 0L; + final ChattingExceptionType expect = ChattingExceptionType.NOT_FOUND_CHATTING; + + // when + final ChattingException actualException = + assertThrowsExactly( + ChattingException.class, + () -> + chattingCommandService.requestAnswer( + notExistChattingId, request)); + + // then + assertEquals(expect, actualException.exceptionType()); + } + + @Test + @DisplayName("messages๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ LAST_MESSAGE_NOT_EXIST ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") + void requestAnswerWithEmptyMessagesTest() { + // given + final List emptyMessages = new ArrayList<>(); + final AnswerRequest emptyMessageRequest = + new AnswerRequest(GPT_3_5_TURBO, emptyMessages); + + final ChattingExceptionType expect = ChattingExceptionType.LAST_MESSAGE_NOT_EXIST; + + // when + final ChattingException actualException = + assertThrowsExactly( + ChattingException.class, + () -> + chattingCommandService.requestAnswer( + chattingId, emptyMessageRequest)); + + // then + assertEquals(expect, actualException.exceptionType()); + } + + @ParameterizedTest + @EnumSource( + value = MessageRole.class, + names = {"SYSTEM", "ASSISTANT"}) + @DisplayName( + "๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€(์งˆ๋ฌธ)์˜ role์ด user๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ BAD_LAST_MESSAGE_ROLE ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") + void requestAnswerWithLastMessageRoleNotUserTest(final MessageRole role) { + // given + final List messages = new ArrayList<>(this.messages); + messages.add(new MessageRequest(role, "์ž˜๋ชป๋œ role์˜ ์งˆ๋ฌธ")); + final AnswerRequest wrongRequest = new AnswerRequest(GPT_3_5_TURBO, messages); + + final ChattingExceptionType expect = ChattingExceptionType.BAD_LAST_MESSAGE_ROLE; + + // when + final ChattingException actualException = + assertThrowsExactly( + ChattingException.class, + () -> chattingCommandService.requestAnswer(chattingId, wrongRequest)); + + // then + assertEquals(expect, actualException.exceptionType()); + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = {" ", "\n", "\t"}) + @DisplayName( + "๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€(์งˆ๋ฌธ)์˜ content๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ CAN_NOT_EMPTY_CONTENT ํƒ€์ž…์˜ ChattingException์ด ๋ฐœ์ƒํ•œ๋‹ค.") + void requestAnswerWithEmptyContentTest(final String content) { + // given + final List messages = new ArrayList<>(this.messages); + messages.add(new MessageRequest(USER, content)); + final AnswerRequest emptyMessageRequest = new AnswerRequest(GPT_3_5_TURBO, messages); + + final ChattingExceptionType expect = ChattingExceptionType.CAN_NOT_EMPTY_CONTENT; + + // when + final ChattingException actualException = + assertThrowsExactly( + ChattingException.class, + () -> + chattingCommandService.requestAnswer( + chattingId, emptyMessageRequest)); + + // then + assertEquals(expect, actualException.exceptionType()); + } } - } } From c2026d4700b9e26b97a6849ad04eef61efbf2066 Mon Sep 17 00:00:00 2001 From: YONGWOOK CHOI <60510921+CYY1007@users.noreply.github.com> Date: Wed, 27 Dec 2023 17:38:37 +0900 Subject: [PATCH 03/28] =?UTF-8?q?=F0=9F=92=9A=20=20Ci=20:=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EC=A0=84=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20github=20action=20=EC=88=98=EC=A0=95=20(#132)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :green_heart: Ci : ์„œ๋ฒ„ ์ด์ „์œผ๋กœ ์ธํ•œ github action ์ˆ˜์ • * spring security ์„ค์ • ๋ณต์› --- .github/workflows/dev_deploy.yml | 10 +++++----- .../java/briefing/exception/common/ApiErrorResult.java | 2 +- .../java/briefing/security/config/SecurityConfig.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml index e86d9ca..24e9adb 100644 --- a/.github/workflows/dev_deploy.yml +++ b/.github/workflows/dev_deploy.yml @@ -54,12 +54,12 @@ jobs: - name: Beanstalk Deploy uses: einaregilsson/beanstalk-deploy@v20 with: - aws_access_key: ${{ secrets.AWS_ACTION_ACCESS_KEY_ID }} - aws_secret_key: ${{ secrets.AWS_ACTION_SECRET_ACCESS_KEY }} - application_name: breifing-dev - environment_name: Breifing-dev-env + aws_access_key: ${{ secrets.AWS_DEV_ACTION_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.AWS_DEV_ACTION_SECRET_ACCESS_KEY }} + application_name: briefing-dev + environment_name: Briefing-dev-env version_label: github-action-${{ steps.current-time.outputs.formattedTime }} - region: ap-northeast-1 + region: ap-northeast-2 deployment_package: deploy/deploy.zip wait_for_deployment: false diff --git a/src/main/java/briefing/exception/common/ApiErrorResult.java b/src/main/java/briefing/exception/common/ApiErrorResult.java index 0274979..023fbf6 100644 --- a/src/main/java/briefing/exception/common/ApiErrorResult.java +++ b/src/main/java/briefing/exception/common/ApiErrorResult.java @@ -9,7 +9,7 @@ @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ApiErrorResult { +public class ApiErrorResult { private Boolean isSuccess; private String code; diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java index 31629d6..1f4e7f6 100644 --- a/src/main/java/briefing/security/config/SecurityConfig.java +++ b/src/main/java/briefing/security/config/SecurityConfig.java @@ -125,4 +125,4 @@ public CorsConfigurationSource corsConfiguration() { return config; }; } -} +} \ No newline at end of file From d1229bfd65d5a8110ef3b354cdf16ab7a97405e7 Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Thu, 28 Dec 2023 10:25:30 +0900 Subject: [PATCH 04/28] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f124d1e..033b49d 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,8 @@
## ๐Ÿ“š ๊ฐœ๋ฐœ ๊ณผ์ • -- [[Briefing] API ๋ฒ„์ „ ๊ด€๋ฆฌ & ์ „๋žต ํŒจํ„ด](https://velog.io/@cekim/briefing-api-versioning) -- [[Briefing] Spotless๋กœ ์ฝ”๋“œ ํฌ๋งท ์œ ์ง€ํ•˜๊ธฐ](https://velog.io/@cekim/briefing-spotless) +- [[Briefing] API ๋ฒ„์ „ ๊ด€๋ฆฌ & ์ „๋žต ํŒจํ„ด](https://poisson-it.tistory.com/75) +- [[Briefing] Spotless๋กœ ์ฝ”๋“œ ํฌ๋งท ์œ ์ง€ํ•˜๊ธฐ](https://poisson-it.tistory.com/77)
From 044a7591b234c3c87bacbdec3204f2ae1989e9ec Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sat, 30 Dec 2023 20:33:50 +0900 Subject: [PATCH 05/28] =?UTF-8?q?:recycle:=20Refactor:=20Scrap=20API=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EB=B3=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/common/ApiErrorResult.java | 2 +- .../java/briefing/scrap/api/ScrapApi.java | 24 ---------- .../java/briefing/scrap/api/ScrapV2Api.java | 48 +++++++++++++++++++ .../security/config/SecurityConfig.java | 2 +- 4 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 src/main/java/briefing/scrap/api/ScrapV2Api.java diff --git a/src/main/java/briefing/exception/common/ApiErrorResult.java b/src/main/java/briefing/exception/common/ApiErrorResult.java index 023fbf6..0274979 100644 --- a/src/main/java/briefing/exception/common/ApiErrorResult.java +++ b/src/main/java/briefing/exception/common/ApiErrorResult.java @@ -9,7 +9,7 @@ @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ApiErrorResult { +public class ApiErrorResult { private Boolean isSuccess; private String code; diff --git a/src/main/java/briefing/scrap/api/ScrapApi.java b/src/main/java/briefing/scrap/api/ScrapApi.java index 8c10535..71a22c4 100644 --- a/src/main/java/briefing/scrap/api/ScrapApi.java +++ b/src/main/java/briefing/scrap/api/ScrapApi.java @@ -29,14 +29,6 @@ public CommonResponse create( return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); } - @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") - @PostMapping("/v2/scraps/briefings") - public CommonResponse createV2( - @RequestBody ScrapRequest.CreateDTO request) { - Scrap createdScrap = scrapCommandService.create(request); - return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); - } - @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V1", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse delete( @@ -45,14 +37,6 @@ public CommonResponse delete( return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); } - @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") - @DeleteMapping("/v2/scraps/briefings/{briefingId}/members/{memberId}") - public CommonResponse deleteV2( - @PathVariable Long briefingId, @PathVariable Long memberId) { - Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); - return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); - } - @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V1", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/scraps/briefings/members/{memberId}") public CommonResponse> getScrapsByMember( @@ -60,12 +44,4 @@ public CommonResponse> getScrapsByMember( List scraps = scrapQueryService.getScrapsByMemberId(memberId); return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTO).toList()); } - - @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") - @GetMapping("/v2/scraps/briefings/members/{memberId}") - public CommonResponse> getScrapsByMemberV2( - @PathVariable Long memberId) { - List scraps = scrapQueryService.getScrapsByMemberId(memberId); - return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTOV2).toList()); - } } diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java new file mode 100644 index 0000000..19cbc9c --- /dev/null +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -0,0 +1,48 @@ +package briefing.scrap.api; + +import java.util.List; + +import org.springframework.web.bind.annotation.*; + +import briefing.common.response.CommonResponse; +import briefing.scrap.application.ScrapCommandService; +import briefing.scrap.application.ScrapQueryService; +import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.application.dto.ScrapResponse; +import briefing.scrap.domain.Scrap; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "05-Scrap V2 ๐Ÿ“", description = "์Šคํฌ๋žฉ ๊ด€๋ จ API V2") +@RestController +@RequiredArgsConstructor +@RequestMapping("/v2") +public class ScrapV2Api { + private final ScrapQueryService scrapQueryService; + private final ScrapCommandService scrapCommandService; + + @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") + @PostMapping("/scraps/briefings") + public CommonResponse createV2( + @RequestBody ScrapRequest.CreateDTO request) { + Scrap createdScrap = scrapCommandService.create(request); + return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); + } + + @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") + @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") + public CommonResponse deleteV2( + @PathVariable Long briefingId, @PathVariable Long memberId) { + Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); + return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); + } + + @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") + @GetMapping("/scraps/briefings/members/{memberId}") + public CommonResponse> getScrapsByMemberV2( + @PathVariable Long memberId) { + List scraps = scrapQueryService.getScrapsByMemberId(memberId); + return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTOV2).toList()); + } +} diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java index 1f4e7f6..31629d6 100644 --- a/src/main/java/briefing/security/config/SecurityConfig.java +++ b/src/main/java/briefing/security/config/SecurityConfig.java @@ -125,4 +125,4 @@ public CorsConfigurationSource corsConfiguration() { return config; }; } -} \ No newline at end of file +} From 546f18da6e12a6a853e6372d4e8bc6c36939faa7 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sat, 30 Dec 2023 20:43:34 +0900 Subject: [PATCH 06/28] =?UTF-8?q?:recycle:=20Refactor:=20Briefing=20API=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EB=B3=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../briefing/briefing/api/BriefingApi.java | 49 --------------- .../briefing/briefing/api/BriefingV2Api.java | 63 +++++++++++++++++++ 2 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 src/main/java/briefing/briefing/api/BriefingV2Api.java diff --git a/src/main/java/briefing/briefing/api/BriefingApi.java b/src/main/java/briefing/briefing/api/BriefingApi.java index f82b668..73d43af 100644 --- a/src/main/java/briefing/briefing/api/BriefingApi.java +++ b/src/main/java/briefing/briefing/api/BriefingApi.java @@ -1,7 +1,5 @@ package briefing.briefing.api; -import java.time.LocalDate; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -13,7 +11,6 @@ import briefing.briefing.application.BriefingQueryService; import briefing.briefing.application.dto.*; import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.BriefingType; import briefing.common.enums.APIVersion; import briefing.common.response.CommonResponse; import briefing.member.domain.Member; @@ -21,7 +18,6 @@ import briefing.security.handler.annotation.AuthMember; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -34,15 +30,6 @@ public class BriefingApi { private final BriefingCommandService briefingCommandService; private final ScrapQueryService scrapQueryService; - @GetMapping("/v2/briefings") - @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V2", description = "") - public CommonResponse findBriefingsV2( - @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params) { - List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2); - return CommonResponse.onSuccess( - BriefingConverter.toBriefingPreviewListDTOV2(params.getDate(), briefingList)); - } - @GetMapping("/briefings") @Parameter(name = "timeOfDay", hidden = true) @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V1", description = "") @@ -54,42 +41,6 @@ public CommonResponse findBriefings( BriefingConverter.toBriefingPreviewListDTO(params.getDate(), briefingList)); } - @Deprecated - @Operation( - summary = "ํ‚ค์›Œ๋“œ ์ „๋‹ฌ V2 ์ž„์‹œ API", - description = "ํ‚ค์›Œ๋“œ ์ „๋‹ฌ V2 ์ž„์‹œ API ์ž…๋‹ˆ๋‹ค. ์‘๋‹ต์€ ๋ฌด์กฐ๊ฑด ๋™์ผํ•ฉ๋‹ˆ๋‹ค. type๋งŒ ์ฃผ์‹ ๊ฑธ ๋‹ด์•„์„œ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.") - @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต") - @GetMapping("/briefings/temp") - public CommonResponse findBriefingsV2Temp( - @RequestParam("type") final BriefingType type, - @RequestParam("date") final LocalDate date) { - List idList = Arrays.asList(346L, 347L, 348L, 349L, 350L); - return CommonResponse.onSuccess( - BriefingConverter.toBriefingPreviewV2TempListDTO(date, idList, type)); - } - - @GetMapping("/v2/briefings/{id}") - @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V2", description = "") - @Parameter(name = "member", hidden = true) - public CommonResponse findBriefingV2( - @PathVariable final Long id, @AuthMember Member member) { - - Boolean isScrap = - Optional.ofNullable(member) - .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) - .orElseGet(() -> Boolean.FALSE); - - Boolean isBriefingOpen = false; - Boolean isWarning = false; - - return CommonResponse.onSuccess( - BriefingConverter.toBriefingDetailDTOV2( - briefingQueryService.findBriefing(id, APIVersion.V2), - isScrap, - isBriefingOpen, - isWarning)); - } - @GetMapping("/briefings/{id}") @Parameter(name = "member", hidden = true) @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V1", description = "") diff --git a/src/main/java/briefing/briefing/api/BriefingV2Api.java b/src/main/java/briefing/briefing/api/BriefingV2Api.java new file mode 100644 index 0000000..a6a80ce --- /dev/null +++ b/src/main/java/briefing/briefing/api/BriefingV2Api.java @@ -0,0 +1,63 @@ +package briefing.briefing.api; + +import java.util.List; +import java.util.Optional; + +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.web.bind.annotation.*; + +import briefing.briefing.application.BriefingCommandService; +import briefing.briefing.application.BriefingQueryService; +import briefing.briefing.application.dto.BriefingRequestParam; +import briefing.briefing.application.dto.BriefingResponseDTO; +import briefing.briefing.domain.Briefing; +import briefing.common.enums.APIVersion; +import briefing.common.response.CommonResponse; +import briefing.member.domain.Member; +import briefing.scrap.application.ScrapQueryService; +import briefing.security.handler.annotation.AuthMember; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "03-Briefing V2 \uD83D\uDCF0", description = "๋ธŒ๋ฆฌํ•‘ ๊ด€๋ จ API V2") +@RestController +@RequiredArgsConstructor +@RequestMapping("/v2") +public class BriefingV2Api { + private final BriefingQueryService briefingQueryService; + private final BriefingCommandService briefingCommandService; + private final ScrapQueryService scrapQueryService; + + @GetMapping("/briefings") + @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V2", description = "") + public CommonResponse findBriefingsV2( + @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params) { + List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2); + return CommonResponse.onSuccess( + BriefingConverter.toBriefingPreviewListDTOV2(params.getDate(), briefingList)); + } + + @GetMapping("/briefings/{id}") + @Operation(summary = "03-02Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‹จ๊ฑด ์กฐํšŒ V2", description = "") + @Parameter(name = "member", hidden = true) + public CommonResponse findBriefingV2( + @PathVariable final Long id, @AuthMember Member member) { + + Boolean isScrap = + Optional.ofNullable(member) + .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) + .orElseGet(() -> Boolean.FALSE); + + Boolean isBriefingOpen = false; + Boolean isWarning = false; + + return CommonResponse.onSuccess( + BriefingConverter.toBriefingDetailDTOV2( + briefingQueryService.findBriefing(id, APIVersion.V2), + isScrap, + isBriefingOpen, + isWarning)); + } +} From ca3662300b12b7c1365f33b6d9d56487a4ce0ff2 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sat, 30 Dec 2023 21:10:21 +0900 Subject: [PATCH 07/28] =?UTF-8?q?:recycle:=20Refactor:=20Member=20API=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EB=B3=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/briefing/member/api/MemberApi.java | 90 ----------- .../java/briefing/member/api/MemberV2Api.java | 142 ++++++++++++++++++ 2 files changed, 142 insertions(+), 90 deletions(-) create mode 100644 src/main/java/briefing/member/api/MemberV2Api.java diff --git a/src/main/java/briefing/member/api/MemberApi.java b/src/main/java/briefing/member/api/MemberApi.java index ae8aac9..3467d37 100644 --- a/src/main/java/briefing/member/api/MemberApi.java +++ b/src/main/java/briefing/member/api/MemberApi.java @@ -87,27 +87,6 @@ public CommonResponse login( MemberConverter.toLoginDTO(member, accessToken, refreshToken)); } - @Operation(summary = "02-01 Member\uD83D\uDC64 ์†Œ์…œ ๋กœ๊ทธ์ธ V2", description = "๊ตฌ๊ธ€, ์• ํ”Œ ์†Œ์…œ๋กœ๊ทธ์ธ API์ž…๋‹ˆ๋‹ค.") - @PostMapping("/v2/members/auth/{socialType}") - public CommonResponse loginV2( - @Parameter(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ข…๋ฅ˜", example = "google") @PathVariable - final SocialType socialType, - @RequestBody final MemberRequest.LoginDTO request) { - Member member = memberCommandService.login(socialType, request); - String accessToken = - tokenProvider.createAccessToken( - member.getId(), - member.getSocialType().toString(), - member.getSocialId(), - List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name()))); - String refreshToken = - redisService - .generateRefreshToken(member.getSocialId(), member.getSocialType()) - .getToken(); - return CommonResponse.onSuccess( - MemberConverter.toLoginDTO(member, accessToken, refreshToken)); - } - @Operation( summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V1", description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") @@ -142,40 +121,6 @@ public CommonResponse reissueToken( parsedMember.getId(), accessToken, refreshToken.getToken())); } - @Operation( - summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V2", - description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") - @ApiResponses({ - @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), - @ApiResponse( - responseCode = "COMMON001", - description = "request body์— ๋‹ด๊ธธ ๊ฐ’์ด ์ด์ƒํ•จ, result๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse( - responseCode = "AUTH005", - description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ, ๋‹ค์‹œ ๋กœ๊ทธ์ธ", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse( - responseCode = "AUTH009", - description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ชจ์–‘์ด ์ž˜๋ชป ๋จ", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - }) - @PostMapping("/v2/members/auth/token") - public CommonResponse reissueTokenV2( - @Valid @RequestBody MemberRequest.ReissueDTO request) { - RefreshToken refreshToken = redisService.reGenerateRefreshToken(request); - Member parsedMember = memberCommandService.parseRefreshToken(refreshToken); - String accessToken = - tokenProvider.createAccessToken( - parsedMember.getId(), - parsedMember.getSocialType().toString(), - parsedMember.getSocialId(), - List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString()))); - return CommonResponse.onSuccess( - MemberConverter.toReIssueTokenDTO( - parsedMember.getId(), accessToken, refreshToken.getToken())); - } - @Operation(summary = "02-01 Member\uD83D\uDC64 ํšŒ์› ํƒˆํ‡ด V1", description = "ํšŒ์› ํƒˆํ‡ด API ์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/members/{memberId}") @Parameters({ @@ -210,39 +155,4 @@ public CommonResponse quitMember( memberCommandService.deleteMember(memberId); return CommonResponse.onSuccess(MemberConverter.toQuitDTO()); } - - @Operation(summary = "02-01 Member\uD83D\uDC64 ํšŒ์› ํƒˆํ‡ด V2", description = "ํšŒ์› ํƒˆํ‡ด API ์ž…๋‹ˆ๋‹ค.") - @DeleteMapping("/v2/members/{memberId}") - @Parameters({ - @Parameter(name = "member", hidden = true), - @Parameter(name = "memberId", description = "์‚ญ์ œ ๋Œ€์ƒ ๋ฉค๋ฒ„์•„์ด๋””") - }) - @ApiResponses({ - @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), - @ApiResponse( - responseCode = "AUTH003", - description = "access ํ† ํฐ์„ ์ฃผ์„ธ์š”!", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse( - responseCode = "AUTH004", - description = "acess ํ† ํฐ ๋งŒ๋ฃŒ", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse( - responseCode = "AUTH006", - description = "acess ํ† ํฐ ๋ชจ์–‘์ด ์ด์ƒํ•จ", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse( - responseCode = "MEMBER_001", - description = "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse( - responseCode = "MEMBER_002", - description = "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ํƒˆํ‡ด ๋Œ€์ƒ์ด ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - }) - public CommonResponse quitMemberV2( - @AuthMember Member member, @CheckSameMember @PathVariable Long memberId) { - memberCommandService.deleteMember(memberId); - return CommonResponse.onSuccess(MemberConverter.toQuitDTO()); - } } diff --git a/src/main/java/briefing/member/api/MemberV2Api.java b/src/main/java/briefing/member/api/MemberV2Api.java new file mode 100644 index 0000000..a916c89 --- /dev/null +++ b/src/main/java/briefing/member/api/MemberV2Api.java @@ -0,0 +1,142 @@ +package briefing.member.api; + +import java.util.List; + +import jakarta.validation.Valid; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import briefing.common.response.CommonResponse; +import briefing.member.application.MemberCommandService; +import briefing.member.application.MemberQueryService; +import briefing.member.application.dto.MemberRequest; +import briefing.member.application.dto.MemberResponse; +import briefing.member.domain.Member; +import briefing.member.domain.MemberRole; +import briefing.member.domain.SocialType; +import briefing.redis.domain.RefreshToken; +import briefing.redis.service.RedisService; +import briefing.security.handler.annotation.AuthMember; +import briefing.security.provider.TokenProvider; +import briefing.validation.annotation.CheckSameMember; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +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; +import lombok.RequiredArgsConstructor; + +@Tag(name = "02-Member V2 \uD83D\uDC64", description = "์‚ฌ์šฉ์ž ๊ด€๋ จ API V2") +@RestController +@Validated +@RequiredArgsConstructor +@ApiResponses({ + @ApiResponse( + responseCode = "COMMON000", + description = "SERVER ERROR, ๋ฐฑ์•ค๋“œ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์•Œ๋ ค์ฃผ์„ธ์š”", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), +}) +@RequestMapping("/v2") +public class MemberV2Api { + private final MemberQueryService memberQueryService; + private final MemberCommandService memberCommandService; + + private final TokenProvider tokenProvider; + + private final RedisService redisService; + + @Operation(summary = "02-01 Member\uD83D\uDC64 ์†Œ์…œ ๋กœ๊ทธ์ธ V2", description = "๊ตฌ๊ธ€, ์• ํ”Œ ์†Œ์…œ๋กœ๊ทธ์ธ API์ž…๋‹ˆ๋‹ค.") + @PostMapping("/members/auth/{socialType}") + public CommonResponse loginV2( + @Parameter(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ข…๋ฅ˜", example = "google") @PathVariable + final SocialType socialType, + @RequestBody final MemberRequest.LoginDTO request) { + Member member = memberCommandService.login(socialType, request); + String accessToken = + tokenProvider.createAccessToken( + member.getId(), + member.getSocialType().toString(), + member.getSocialId(), + List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name()))); + String refreshToken = + redisService + .generateRefreshToken(member.getSocialId(), member.getSocialType()) + .getToken(); + return CommonResponse.onSuccess( + MemberConverter.toLoginDTO(member, accessToken, refreshToken)); + } + + @Operation( + summary = "02-01 Member\uD83D\uDC64 accessToken ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ V2", + description = "accessToken ๋งŒ๋ฃŒ ์‹œ refreshToken์œผ๋กœ ์žฌ๋ฐœ๊ธ‰์„ ๋ฐ›๋Š” API ์ž…๋‹ˆ๋‹ค.") + @ApiResponses({ + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), + @ApiResponse( + responseCode = "COMMON001", + description = "request body์— ๋‹ด๊ธธ ๊ฐ’์ด ์ด์ƒํ•จ, result๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”!", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH005", + description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ, ๋‹ค์‹œ ๋กœ๊ทธ์ธ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH009", + description = "๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ชจ์–‘์ด ์ž˜๋ชป ๋จ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @PostMapping("/members/auth/token") + public CommonResponse reissueTokenV2( + @Valid @RequestBody MemberRequest.ReissueDTO request) { + RefreshToken refreshToken = redisService.reGenerateRefreshToken(request); + Member parsedMember = memberCommandService.parseRefreshToken(refreshToken); + String accessToken = + tokenProvider.createAccessToken( + parsedMember.getId(), + parsedMember.getSocialType().toString(), + parsedMember.getSocialId(), + List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString()))); + return CommonResponse.onSuccess( + MemberConverter.toReIssueTokenDTO( + parsedMember.getId(), accessToken, refreshToken.getToken())); + } + + @Operation(summary = "02-01 Member\uD83D\uDC64 ํšŒ์› ํƒˆํ‡ด V2", description = "ํšŒ์› ํƒˆํ‡ด API ์ž…๋‹ˆ๋‹ค.") + @DeleteMapping("/members/{memberId}") + @Parameters({ + @Parameter(name = "member", hidden = true), + @Parameter(name = "memberId", description = "์‚ญ์ œ ๋Œ€์ƒ ๋ฉค๋ฒ„์•„์ด๋””") + }) + @ApiResponses({ + @ApiResponse(responseCode = "1000", description = "OK, ์„ฑ๊ณต"), + @ApiResponse( + responseCode = "AUTH003", + description = "access ํ† ํฐ์„ ์ฃผ์„ธ์š”!", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH004", + description = "acess ํ† ํฐ ๋งŒ๋ฃŒ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "AUTH006", + description = "acess ํ† ํฐ ๋ชจ์–‘์ด ์ด์ƒํ•จ", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "MEMBER_001", + description = "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse( + responseCode = "MEMBER_002", + description = "๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์™€ ํƒˆํ‡ด ๋Œ€์ƒ์ด ๋™์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse quitMemberV2( + @AuthMember Member member, @CheckSameMember @PathVariable Long memberId) { + memberCommandService.deleteMember(memberId); + return CommonResponse.onSuccess(MemberConverter.toQuitDTO()); + } +} From b3cbaf7c34e893d7d67c851f119481f7ec873490 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Sat, 30 Dec 2023 21:53:51 +0900 Subject: [PATCH 08/28] =?UTF-8?q?:recycle:=20Refactor=20:=20Swagger=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20=EB=A6=AC=ED=84=B0?= =?UTF-8?q?=EC=B9=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/briefing/BriefingApplication.java | 7 +++ .../java/briefing/config/SwaggerConfig.java | 19 ++++++- .../security/config/SecurityConfig.java | 57 +++++++++++++++++-- .../handler/SwaggerLoginSuccessHandler.java | 17 ++++++ src/main/resources/application.yml | 4 ++ 5 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java diff --git a/src/main/java/briefing/BriefingApplication.java b/src/main/java/briefing/BriefingApplication.java index 402c52b..c8dc092 100644 --- a/src/main/java/briefing/BriefingApplication.java +++ b/src/main/java/briefing/BriefingApplication.java @@ -1,5 +1,7 @@ package briefing; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.servers.Server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -7,6 +9,11 @@ import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +@OpenAPIDefinition(servers = {@Server(url = "http://localhost:8080", description = "local server"), + @Server(url = "https://dev.briefing.store", description = "dev server"), + @Server(url = "https://api.newsbreifing.store", description = "release server") +}) + @SpringBootApplication @EnableFeignClients @EnableRedisRepositories diff --git a/src/main/java/briefing/config/SwaggerConfig.java b/src/main/java/briefing/config/SwaggerConfig.java index 68369d1..384ae67 100644 --- a/src/main/java/briefing/config/SwaggerConfig.java +++ b/src/main/java/briefing/config/SwaggerConfig.java @@ -15,8 +15,23 @@ public class SwaggerConfig { @Bean - public GroupedOpenApi publicApi() { - return GroupedOpenApi.builder().group("v1-definition").packagesToScan("briefing").build(); + public GroupedOpenApi v1Api() { + String[] paths = { "/**" }; + + return GroupedOpenApi.builder() + .group("Briefing V1 API") + .pathsToMatch(paths) + .build(); + } + + @Bean + public GroupedOpenApi v2Api() { + String[] paths = { "/v2/**" }; + + return GroupedOpenApi.builder() + .group("Briefing V2 API") + .pathsToMatch(paths) + .build(); } @Bean diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java index 1f4e7f6..5a99d87 100644 --- a/src/main/java/briefing/security/config/SecurityConfig.java +++ b/src/main/java/briefing/security/config/SecurityConfig.java @@ -4,18 +4,28 @@ import java.util.Collections; +import briefing.security.handler.SwaggerLoginSuccessHandler; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; @@ -46,6 +56,14 @@ public class SecurityConfig { private static final String[] WHITE_LIST = {}; + private final SwaggerLoginSuccessHandler swaggerLoginSuccessHandler = new SwaggerLoginSuccessHandler(); + + @Value("${swagger.login.id}") + private String swaggerId; + + @Value("${swagger.login.password}") + private String swaggerPass; + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -64,17 +82,35 @@ public WebSecurityCustomizer webSecurityCustomizer() { "", "/", "/schedule", - "/swagger-ui.html", "/v3/api-docs", "/v3/api-docs/**", - "/swagger-ui/index.html", - "/swagger-ui/**", "/docs/**", "/briefings/temp"); } @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + @Order(1) + public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception { + return http.securityMatcher("/swagger-ui/**", "/login") + .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfiguration())) + .httpBasic(withDefaults()) + .csrf(AbstractHttpConfigurer::disable) // ๋น„ํ™œ์„ฑํ™” + .sessionManagement( + manage -> + manage.sessionCreationPolicy( + SessionCreationPolicy.IF_REQUIRED) + ) + .formLogin(authorize->authorize + .successHandler(swaggerLoginSuccessHandler) + .defaultSuccessUrl("/swagger-ui/index.html") + .permitAll()) + .authorizeHttpRequests(authorize-> authorize.requestMatchers("/swagger-ui/index.html").authenticated() + .anyRequest().permitAll()) + .build(); + } + + @Bean + public SecurityFilterChain JwtFilterChain(HttpSecurity http) throws Exception { return http.cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfiguration())) .httpBasic(withDefaults()) .csrf(AbstractHttpConfigurer::disable) // ๋น„ํ™œ์„ฑํ™” @@ -82,7 +118,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { manage -> manage.sessionCreationPolicy( SessionCreationPolicy.STATELESS)) // Session ์‚ฌ์šฉ ์•ˆํ•จ - .formLogin(AbstractHttpConfigurer::disable) // form login ์‚ฌ์šฉ ์•ˆํ•จ + .formLogin(AbstractHttpConfigurer::disable) .authorizeHttpRequests( authorize -> { authorize @@ -114,6 +150,17 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .build(); } + @Bean + public UserDetailsService userDetailsService() { + UserDetails userDetails = User.builder() + .username(swaggerId) + .password(passwordEncoder().encode(swaggerPass)) + .roles("USER", "ADMIN") + .build(); + + return new InMemoryUserDetailsManager(userDetails); + } + public CorsConfigurationSource corsConfiguration() { return request -> { org.springframework.web.cors.CorsConfiguration config = diff --git a/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java b/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java new file mode 100644 index 0000000..c13d177 --- /dev/null +++ b/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java @@ -0,0 +1,17 @@ +package briefing.security.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import java.io.IOException; + +public class SwaggerLoginSuccessHandler implements AuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + request.getSession().setMaxInactiveInterval(120); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 16dff82..a93b913 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,10 @@ springdoc: cache: disabled: true use-fqn: true +swagger: + login: + id: ${SWAGGER_ID} + password : ${SWAGGER_PASS} --- spring: config: From 3baf756dd774c1c1f7551bb57c7379fd579f0279 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sat, 30 Dec 2023 23:13:58 +0900 Subject: [PATCH 09/28] =?UTF-8?q?:sparkles:=20Feat:=20Scrap=20Command=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=ED=8C=A8=ED=84=B4=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/briefing/scrap/api/ScrapApi.java | 5 +-- .../java/briefing/scrap/api/ScrapV2Api.java | 5 +-- .../application/ScrapCommandService.java | 5 +-- .../context/ScrapCommandContext.java | 20 +++++++++++ .../context/ScrapCommandContextFactory.java | 33 ++++++++++++++++++ .../strategy/ScrapCommandStrategy.java | 11 ++++++ .../strategy/ScrapV1CommandStrategy.java | 34 +++++++++++++++++++ .../strategy/ScrapV2CommandStrategy.java | 34 +++++++++++++++++++ 8 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 src/main/java/briefing/scrap/application/context/ScrapCommandContext.java create mode 100644 src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java create mode 100644 src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java create mode 100644 src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java create mode 100644 src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java diff --git a/src/main/java/briefing/scrap/api/ScrapApi.java b/src/main/java/briefing/scrap/api/ScrapApi.java index 71a22c4..85e2ae5 100644 --- a/src/main/java/briefing/scrap/api/ScrapApi.java +++ b/src/main/java/briefing/scrap/api/ScrapApi.java @@ -2,6 +2,7 @@ import java.util.List; +import briefing.common.enums.APIVersion; import org.springframework.web.bind.annotation.*; import briefing.common.response.CommonResponse; @@ -25,7 +26,7 @@ public class ScrapApi { @PostMapping("/scraps/briefings") public CommonResponse create( @RequestBody ScrapRequest.CreateDTO request) { - Scrap createdScrap = scrapCommandService.create(request); + Scrap createdScrap = scrapCommandService.create(request, APIVersion.V1); return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); } @@ -33,7 +34,7 @@ public CommonResponse create( @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse delete( @PathVariable Long briefingId, @PathVariable Long memberId) { - Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); + Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId, APIVersion.V1); return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); } diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index 19cbc9c..ad83f17 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -2,6 +2,7 @@ import java.util.List; +import briefing.common.enums.APIVersion; import org.springframework.web.bind.annotation.*; import briefing.common.response.CommonResponse; @@ -26,7 +27,7 @@ public class ScrapV2Api { @PostMapping("/scraps/briefings") public CommonResponse createV2( @RequestBody ScrapRequest.CreateDTO request) { - Scrap createdScrap = scrapCommandService.create(request); + Scrap createdScrap = scrapCommandService.create(request, APIVersion.V2); return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); } @@ -34,7 +35,7 @@ public CommonResponse createV2( @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse deleteV2( @PathVariable Long briefingId, @PathVariable Long memberId) { - Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); + Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId, APIVersion.V2); return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); } diff --git a/src/main/java/briefing/scrap/application/ScrapCommandService.java b/src/main/java/briefing/scrap/application/ScrapCommandService.java index 9dff2ed..007bee4 100644 --- a/src/main/java/briefing/scrap/application/ScrapCommandService.java +++ b/src/main/java/briefing/scrap/application/ScrapCommandService.java @@ -1,5 +1,6 @@ package briefing.scrap.application; +import briefing.common.enums.APIVersion; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,7 +28,7 @@ public class ScrapCommandService { private final MemberRepository memberRepository; private final BriefingRepository briefingRepository; - public Scrap create(ScrapRequest.CreateDTO request) { + public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ if (scrapRepository.existsByMember_IdAndBriefing_Id( request.getMemberId(), request.getBriefingId())) @@ -55,7 +56,7 @@ public Scrap create(ScrapRequest.CreateDTO request) { } } - public Scrap delete(Long briefingId, Long memberId) { + public Scrap delete(Long briefingId, Long memberId, APIVersion version) { Scrap scrap = scrapRepository .findByBriefing_IdAndMember_Id(briefingId, memberId) diff --git a/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java b/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java new file mode 100644 index 0000000..8e7e5aa --- /dev/null +++ b/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java @@ -0,0 +1,20 @@ +package briefing.scrap.application.context; + +import briefing.common.enums.APIVersion; +import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.application.strategy.ScrapCommandStrategy; +import briefing.scrap.domain.Scrap; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class ScrapCommandContext { + private final ScrapCommandStrategy scrapCommandStrategy; + + public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { + return scrapCommandStrategy.create(request, version); + } + + public Scrap delete(Long briefingId, Long memberId, APIVersion version) { + return scrapCommandStrategy.delete(briefingId, memberId, version); + } +} diff --git a/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java b/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java new file mode 100644 index 0000000..6d47636 --- /dev/null +++ b/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java @@ -0,0 +1,33 @@ +package briefing.scrap.application.context; + +import briefing.common.enums.APIVersion; +import briefing.scrap.application.strategy.ScrapCommandStrategy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +@Component +public class ScrapCommandContextFactory { + + private final Map contextMap; + + @Autowired + public ScrapCommandContextFactory(List strategies) { + contextMap = new EnumMap<>(APIVersion.class); + for (ScrapCommandStrategy strategy : strategies) { + APIVersion version = strategy.getVersion(); + contextMap.put(version, new ScrapCommandContext(strategy)); + } + } + + public ScrapCommandContext getContextByVersion(APIVersion version) { + ScrapCommandContext context = contextMap.get(version); + if (context == null) { + throw new IllegalArgumentException("Invalid API version: " + version); + } + return context; + } +} \ No newline at end of file diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java new file mode 100644 index 0000000..8b0cda5 --- /dev/null +++ b/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java @@ -0,0 +1,11 @@ +package briefing.scrap.application.strategy; + +import briefing.common.enums.APIVersion; +import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.domain.Scrap; + +public interface ScrapCommandStrategy { + Scrap create(ScrapRequest.CreateDTO request, APIVersion version); + Scrap delete(Long briefingId, Long memberId, APIVersion version); + APIVersion getVersion(); +} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java new file mode 100644 index 0000000..7412e71 --- /dev/null +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java @@ -0,0 +1,34 @@ +package briefing.scrap.application.strategy; + +import briefing.briefing.domain.repository.BriefingRepository; +import briefing.common.enums.APIVersion; +import briefing.member.domain.repository.MemberRepository; +import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.domain.Scrap; +import briefing.scrap.domain.repository.ScrapRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ScrapV1CommandStrategy implements ScrapCommandStrategy { + + private final ScrapRepository scrapRepository; + private final MemberRepository memberRepository; + private final BriefingRepository briefingRepository; + + @Override + public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { + return null; + } + + @Override + public Scrap delete(Long briefingId, Long memberId, APIVersion version) { + return null; + } + + @Override + public APIVersion getVersion() { + return null; + } +} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java new file mode 100644 index 0000000..8a0b036 --- /dev/null +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java @@ -0,0 +1,34 @@ +package briefing.scrap.application.strategy; + +import briefing.briefing.domain.repository.BriefingRepository; +import briefing.common.enums.APIVersion; +import briefing.member.domain.repository.MemberRepository; +import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.domain.Scrap; +import briefing.scrap.domain.repository.ScrapRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ScrapV2CommandStrategy implements ScrapCommandStrategy { + + private final ScrapRepository scrapRepository; + private final MemberRepository memberRepository; + private final BriefingRepository briefingRepository; + + @Override + public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { + return null; + } + + @Override + public Scrap delete(Long briefingId, Long memberId, APIVersion version) { + return null; + } + + @Override + public APIVersion getVersion() { + return null; + } +} From b77f6861adcd7493cb012055cfb0f3b12574a3b7 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Sun, 31 Dec 2023 09:47:14 +0900 Subject: [PATCH 10/28] =?UTF-8?q?:bug:=20Fix=20:=20Swagger=20V1=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/briefing/config/SwaggerConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/briefing/config/SwaggerConfig.java b/src/main/java/briefing/config/SwaggerConfig.java index 384ae67..e7162f2 100644 --- a/src/main/java/briefing/config/SwaggerConfig.java +++ b/src/main/java/briefing/config/SwaggerConfig.java @@ -10,17 +10,18 @@ import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; +import org.springframework.core.annotation.Order; +import springfox.documentation.builders.PathSelectors; @Configuration public class SwaggerConfig { @Bean public GroupedOpenApi v1Api() { - String[] paths = { "/**" }; return GroupedOpenApi.builder() .group("Briefing V1 API") - .pathsToMatch(paths) + .pathsToExclude("/v2/**") .build(); } From 500337baf4299ae0f57c88402cd222b2a8880fad Mon Sep 17 00:00:00 2001 From: swa07016 Date: Mon, 1 Jan 2024 18:56:00 +0900 Subject: [PATCH 11/28] =?UTF-8?q?:sparkles:=20Feat:=20Scrap=20V1=20?= =?UTF-8?q?=EC=BB=A4=EB=A7=A8=EB=93=9C=20=EC=A0=84=EB=9E=B5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/briefing/scrap/api/ScrapApi.java | 2 +- .../java/briefing/scrap/api/ScrapV2Api.java | 2 +- .../application/ScrapCommandService.java | 56 ++++--------------- .../context/ScrapCommandContext.java | 9 ++- .../context/ScrapCommandContextFactory.java | 13 +++-- .../strategy/ScrapCommandStrategy.java | 6 +- .../strategy/ScrapV1CommandStrategy.java | 50 +++++++++++++++-- .../strategy/ScrapV2CommandStrategy.java | 9 +-- 8 files changed, 76 insertions(+), 71 deletions(-) diff --git a/src/main/java/briefing/scrap/api/ScrapApi.java b/src/main/java/briefing/scrap/api/ScrapApi.java index 85e2ae5..e8e16ce 100644 --- a/src/main/java/briefing/scrap/api/ScrapApi.java +++ b/src/main/java/briefing/scrap/api/ScrapApi.java @@ -2,9 +2,9 @@ import java.util.List; -import briefing.common.enums.APIVersion; import org.springframework.web.bind.annotation.*; +import briefing.common.enums.APIVersion; import briefing.common.response.CommonResponse; import briefing.scrap.application.ScrapCommandService; import briefing.scrap.application.ScrapQueryService; diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index ad83f17..95f336d 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -2,9 +2,9 @@ import java.util.List; -import briefing.common.enums.APIVersion; import org.springframework.web.bind.annotation.*; +import briefing.common.enums.APIVersion; import briefing.common.response.CommonResponse; import briefing.scrap.application.ScrapCommandService; import briefing.scrap.application.ScrapQueryService; diff --git a/src/main/java/briefing/scrap/application/ScrapCommandService.java b/src/main/java/briefing/scrap/application/ScrapCommandService.java index 007bee4..4ddaa18 100644 --- a/src/main/java/briefing/scrap/application/ScrapCommandService.java +++ b/src/main/java/briefing/scrap/application/ScrapCommandService.java @@ -1,22 +1,13 @@ package briefing.scrap.application; -import briefing.common.enums.APIVersion; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.repository.BriefingRepository; -import briefing.exception.ErrorCode; -import briefing.exception.handler.BriefingException; -import briefing.member.domain.Member; -import briefing.member.domain.repository.MemberRepository; -import briefing.member.exception.MemberException; -import briefing.scrap.api.ScrapConverter; +import briefing.common.enums.APIVersion; +import briefing.scrap.application.context.ScrapCommandContext; +import briefing.scrap.application.context.ScrapCommandContextFactory; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.domain.Scrap; -import briefing.scrap.domain.repository.ScrapRepository; -import briefing.scrap.exception.ScrapException; import lombok.RequiredArgsConstructor; @Service @@ -24,44 +15,17 @@ @RequiredArgsConstructor public class ScrapCommandService { - private final ScrapRepository scrapRepository; - private final MemberRepository memberRepository; - private final BriefingRepository briefingRepository; + private final ScrapCommandContextFactory scrapCommandContextFactory; public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { - // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ - if (scrapRepository.existsByMember_IdAndBriefing_Id( - request.getMemberId(), request.getBriefingId())) - throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); - - Member member = - memberRepository - .findById(request.getMemberId()) - .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); - - Briefing briefing = - briefingRepository - .findById(request.getBriefingId()) - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); - - Scrap scrap = ScrapConverter.toScrap(member, briefing); - - // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ - try { - // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ - return scrapRepository.save(scrap); - } catch (DataIntegrityViolationException e) { - // ์ค‘๋ณต ์Šคํฌ๋žฉ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ - throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); - } + ScrapCommandContext scrapCommandContext = + scrapCommandContextFactory.getContextByVersion(version); + return scrapCommandContext.create(request); } public Scrap delete(Long briefingId, Long memberId, APIVersion version) { - Scrap scrap = - scrapRepository - .findByBriefing_IdAndMember_Id(briefingId, memberId) - .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); - scrapRepository.delete(scrap); - return scrap; + ScrapCommandContext scrapCommandContext = + scrapCommandContextFactory.getContextByVersion(version); + return scrapCommandContext.delete(briefingId, memberId); } } diff --git a/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java b/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java index 8e7e5aa..dc191fe 100644 --- a/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java +++ b/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java @@ -1,6 +1,5 @@ package briefing.scrap.application.context; -import briefing.common.enums.APIVersion; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.application.strategy.ScrapCommandStrategy; import briefing.scrap.domain.Scrap; @@ -10,11 +9,11 @@ public class ScrapCommandContext { private final ScrapCommandStrategy scrapCommandStrategy; - public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { - return scrapCommandStrategy.create(request, version); + public Scrap create(ScrapRequest.CreateDTO request) { + return scrapCommandStrategy.create(request); } - public Scrap delete(Long briefingId, Long memberId, APIVersion version) { - return scrapCommandStrategy.delete(briefingId, memberId, version); + public Scrap delete(Long briefingId, Long memberId) { + return scrapCommandStrategy.delete(briefingId, memberId); } } diff --git a/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java b/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java index 6d47636..d2e516d 100644 --- a/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java +++ b/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java @@ -1,14 +1,15 @@ package briefing.scrap.application.context; -import briefing.common.enums.APIVersion; -import briefing.scrap.application.strategy.ScrapCommandStrategy; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import briefing.common.enums.APIVersion; +import briefing.scrap.application.strategy.ScrapCommandStrategy; + @Component public class ScrapCommandContextFactory { @@ -30,4 +31,4 @@ public ScrapCommandContext getContextByVersion(APIVersion version) { } return context; } -} \ No newline at end of file +} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java index 8b0cda5..80baba1 100644 --- a/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java +++ b/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java @@ -5,7 +5,9 @@ import briefing.scrap.domain.Scrap; public interface ScrapCommandStrategy { - Scrap create(ScrapRequest.CreateDTO request, APIVersion version); - Scrap delete(Long briefingId, Long memberId, APIVersion version); + Scrap create(ScrapRequest.CreateDTO request); + + Scrap delete(Long briefingId, Long memberId); + APIVersion getVersion(); } diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java index 7412e71..5f94975 100644 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java @@ -1,13 +1,22 @@ package briefing.scrap.application.strategy; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Component; + +import briefing.briefing.domain.Briefing; import briefing.briefing.domain.repository.BriefingRepository; import briefing.common.enums.APIVersion; +import briefing.exception.ErrorCode; +import briefing.exception.handler.BriefingException; +import briefing.member.domain.Member; import briefing.member.domain.repository.MemberRepository; +import briefing.member.exception.MemberException; +import briefing.scrap.api.ScrapConverter; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.domain.Scrap; import briefing.scrap.domain.repository.ScrapRepository; +import briefing.scrap.exception.ScrapException; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor @@ -18,17 +27,46 @@ public class ScrapV1CommandStrategy implements ScrapCommandStrategy { private final BriefingRepository briefingRepository; @Override - public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { - return null; + public Scrap create(ScrapRequest.CreateDTO request) { + // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ + if (scrapRepository.existsByMember_IdAndBriefing_Id( + request.getMemberId(), request.getBriefingId())) + throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); + + Member member = + memberRepository + .findById(request.getMemberId()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + + Briefing briefing = + briefingRepository + .findById(request.getBriefingId()) + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + + Scrap scrap = ScrapConverter.toScrap(member, briefing); + + // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ + try { + // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ + return scrapRepository.save(scrap); + } catch (DataIntegrityViolationException e) { + // ์ค‘๋ณต ์Šคํฌ๋žฉ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ + throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); + } } @Override - public Scrap delete(Long briefingId, Long memberId, APIVersion version) { - return null; + public Scrap delete(Long briefingId, Long memberId) { + Scrap scrap = + scrapRepository + .findByBriefing_IdAndMember_Id(briefingId, memberId) + .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); + scrapRepository.delete(scrap); + return scrap; } @Override public APIVersion getVersion() { - return null; + return APIVersion.V1; } } diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java index 8a0b036..251d569 100644 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java @@ -1,5 +1,7 @@ package briefing.scrap.application.strategy; +import org.springframework.stereotype.Component; + import briefing.briefing.domain.repository.BriefingRepository; import briefing.common.enums.APIVersion; import briefing.member.domain.repository.MemberRepository; @@ -7,7 +9,6 @@ import briefing.scrap.domain.Scrap; import briefing.scrap.domain.repository.ScrapRepository; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor @@ -18,17 +19,17 @@ public class ScrapV2CommandStrategy implements ScrapCommandStrategy { private final BriefingRepository briefingRepository; @Override - public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { + public Scrap create(ScrapRequest.CreateDTO request) { return null; } @Override - public Scrap delete(Long briefingId, Long memberId, APIVersion version) { + public Scrap delete(Long briefingId, Long memberId) { return null; } @Override public APIVersion getVersion() { - return null; + return APIVersion.V2; } } From e1a708955e5cb9d36a11b303a65d2825df024e43 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Mon, 1 Jan 2024 19:26:47 +0900 Subject: [PATCH 12/28] =?UTF-8?q?:sparkles:=20Feat:=20Scrap=20V2=20?= =?UTF-8?q?=EC=BB=A4=EB=A7=A8=EB=93=9C=20=EC=A0=84=EB=9E=B5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategy/ScrapV2CommandStrategy.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java index 251d569..439d5de 100644 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java @@ -1,13 +1,21 @@ package briefing.scrap.application.strategy; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; +import briefing.briefing.domain.Briefing; import briefing.briefing.domain.repository.BriefingRepository; import briefing.common.enums.APIVersion; +import briefing.exception.ErrorCode; +import briefing.exception.handler.BriefingException; +import briefing.member.domain.Member; import briefing.member.domain.repository.MemberRepository; +import briefing.member.exception.MemberException; +import briefing.scrap.api.ScrapConverter; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.domain.Scrap; import briefing.scrap.domain.repository.ScrapRepository; +import briefing.scrap.exception.ScrapException; import lombok.RequiredArgsConstructor; @Component @@ -20,12 +28,41 @@ public class ScrapV2CommandStrategy implements ScrapCommandStrategy { @Override public Scrap create(ScrapRequest.CreateDTO request) { - return null; + // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ + if (scrapRepository.existsByMember_IdAndBriefing_Id( + request.getMemberId(), request.getBriefingId())) + throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); + + Member member = + memberRepository + .findById(request.getMemberId()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + + Briefing briefing = + briefingRepository + .findById(request.getBriefingId()) + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + + Scrap scrap = ScrapConverter.toScrap(member, briefing); + + // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ + try { + // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ + return scrapRepository.save(scrap); + } catch (DataIntegrityViolationException e) { + // ์ค‘๋ณต ์Šคํฌ๋žฉ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ + throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); + } } @Override public Scrap delete(Long briefingId, Long memberId) { - return null; + Scrap scrap = + scrapRepository + .findByBriefing_IdAndMember_Id(briefingId, memberId) + .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); + scrapRepository.delete(scrap); + return scrap; } @Override From f44395a70a8e0579683d55087e54453ef99cc640 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Mon, 1 Jan 2024 22:14:32 +0900 Subject: [PATCH 13/28] =?UTF-8?q?:sparkles:=20Scrap=20V2=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../briefing/exception/ExceptionAdvice.java | 4 +- .../briefing/scrap/api/ScrapConverter.java | 21 +++++++++ .../java/briefing/scrap/api/ScrapV2Api.java | 9 ++-- .../scrap/application/dto/ScrapResponse.java | 24 ++++++++++ .../scrap/application/dto/ScrapV2.java | 26 +++++++++++ .../strategy/ScrapV1CommandStrategy.java | 38 +++------------- .../strategy/ScrapV2CommandStrategy.java | 45 +++++-------------- .../strategy/helper/ScrapCreationHelper.java | 43 ++++++++++++++++++ .../strategy/helper/ScrapDeletionHelper.java | 24 ++++++++++ .../repository/ScrapCustomRepository.java | 3 ++ .../repository/ScrapCustomRepositoryImpl.java | 6 +++ .../domain/repository/ScrapRepository.java | 4 +- 12 files changed, 175 insertions(+), 72 deletions(-) create mode 100644 src/main/java/briefing/scrap/application/dto/ScrapV2.java create mode 100644 src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java create mode 100644 src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java create mode 100644 src/main/java/briefing/scrap/domain/repository/ScrapCustomRepository.java create mode 100644 src/main/java/briefing/scrap/domain/repository/ScrapCustomRepositoryImpl.java diff --git a/src/main/java/briefing/exception/ExceptionAdvice.java b/src/main/java/briefing/exception/ExceptionAdvice.java index 5a79dbb..25d17f2 100644 --- a/src/main/java/briefing/exception/ExceptionAdvice.java +++ b/src/main/java/briefing/exception/ExceptionAdvice.java @@ -31,7 +31,7 @@ @RestControllerAdvice(annotations = {RestController.class}) public class ExceptionAdvice extends ResponseEntityExceptionHandler { - @org.springframework.web.bind.annotation.ExceptionHandler + @ExceptionHandler public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { String errorMessage = e.getConstraintViolations().stream() @@ -73,7 +73,7 @@ public ResponseEntity handleMethodArgumentNotValid( ex, HttpHeaders.EMPTY, ErrorCode.valueOf("_BAD_REQUEST"), request, errors); } - @org.springframework.web.bind.annotation.ExceptionHandler + @ExceptionHandler public ResponseEntity exception(Exception e, WebRequest request) { e.printStackTrace(); diff --git a/src/main/java/briefing/scrap/api/ScrapConverter.java b/src/main/java/briefing/scrap/api/ScrapConverter.java index a603a2d..37c43c8 100644 --- a/src/main/java/briefing/scrap/api/ScrapConverter.java +++ b/src/main/java/briefing/scrap/api/ScrapConverter.java @@ -5,6 +5,7 @@ import briefing.briefing.domain.Briefing; import briefing.member.domain.Member; import briefing.scrap.application.dto.ScrapResponse; +import briefing.scrap.application.dto.ScrapV2; import briefing.scrap.domain.Scrap; public class ScrapConverter { @@ -17,6 +18,17 @@ public static ScrapResponse.CreateDTO toCreateDTO(Scrap createdScrap) { .build(); } + public static ScrapResponse.CreateDTOV2 toCreateDTOV2(ScrapV2 createdScrap) { + return ScrapResponse.CreateDTOV2.builder() + .scrapId(createdScrap.getId()) + .memberId(createdScrap.getMember().getId()) + .briefingId(createdScrap.getBriefing().getId()) + .scrapCount(createdScrap.getScrapCount()) + .isScrap(createdScrap.getIsScrap()) + .createdAt(createdScrap.getCreatedAt()) + .build(); + } + public static Scrap toScrap(Member member, Briefing briefing) { return Scrap.builder().member(member).briefing(briefing).build(); } @@ -28,6 +40,15 @@ public static ScrapResponse.DeleteDTO toDeleteDTO(Scrap deletedScrap) { .build(); } + public static ScrapResponse.DeleteDTOV2 toDeleteDTOV2(ScrapV2 deletedScrap) { + return ScrapResponse.DeleteDTOV2.builder() + .scrapId(deletedScrap.getId()) + .isScrap(deletedScrap.getIsScrap()) + .scrapCount(deletedScrap.getScrapCount()) + .deletedAt(LocalDateTime.now()) + .build(); + } + public static ScrapResponse.ReadDTO toReadDTO(Scrap scrap) { return ScrapResponse.ReadDTO.builder() .briefingId(scrap.getBriefing().getId()) diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index 95f336d..695bb14 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -10,6 +10,7 @@ import briefing.scrap.application.ScrapQueryService; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.application.dto.ScrapResponse; +import briefing.scrap.application.dto.ScrapV2; import briefing.scrap.domain.Scrap; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -25,18 +26,18 @@ public class ScrapV2Api { @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/scraps/briefings") - public CommonResponse createV2( + public CommonResponse createV2( @RequestBody ScrapRequest.CreateDTO request) { Scrap createdScrap = scrapCommandService.create(request, APIVersion.V2); - return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); + return CommonResponse.onSuccess(ScrapConverter.toCreateDTOV2((ScrapV2) createdScrap)); } @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") - public CommonResponse deleteV2( + public CommonResponse deleteV2( @PathVariable Long briefingId, @PathVariable Long memberId) { Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId, APIVersion.V2); - return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); + return CommonResponse.onSuccess(ScrapConverter.toDeleteDTOV2((ScrapV2) deletedScrap)); } @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") diff --git a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java index cc5ba42..b5d6fb3 100644 --- a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java +++ b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java @@ -23,6 +23,19 @@ public static class CreateDTO { private LocalDateTime createdAt; } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CreateDTOV2 { + private Long scrapId; + private Long memberId; + private Long briefingId; + private Boolean isScrap; + private Integer scrapCount; + private LocalDateTime createdAt; + } + @Builder @Getter @NoArgsConstructor @@ -32,6 +45,17 @@ public static class DeleteDTO { private LocalDateTime deletedAt; } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class DeleteDTOV2 { + private Long scrapId; + private Boolean isScrap; + private Integer scrapCount; + private LocalDateTime deletedAt; + } + @Builder @Getter @NoArgsConstructor diff --git a/src/main/java/briefing/scrap/application/dto/ScrapV2.java b/src/main/java/briefing/scrap/application/dto/ScrapV2.java new file mode 100644 index 0000000..a33fa72 --- /dev/null +++ b/src/main/java/briefing/scrap/application/dto/ScrapV2.java @@ -0,0 +1,26 @@ +package briefing.scrap.application.dto; + +import java.time.LocalDateTime; + +import briefing.scrap.domain.Scrap; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ScrapV2 extends Scrap { + private Integer scrapCount; + private Boolean isScrap; + private LocalDateTime createdAt; + + private ScrapV2(Scrap scrap, Integer scrapCount, Boolean isScrap) { + super(scrap.getId(), scrap.getMember(), scrap.getBriefing()); + this.scrapCount = scrapCount; + this.isScrap = isScrap; + this.createdAt = scrap.getCreatedAt(); + } + + public static ScrapV2 of(Scrap scrap, Integer scrapCount, Boolean isScrap) { + return new ScrapV2(scrap, scrapCount, isScrap); + } +} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java index 5f94975..cb8ea7c 100644 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java @@ -3,16 +3,11 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.repository.BriefingRepository; import briefing.common.enums.APIVersion; import briefing.exception.ErrorCode; -import briefing.exception.handler.BriefingException; -import briefing.member.domain.Member; -import briefing.member.domain.repository.MemberRepository; -import briefing.member.exception.MemberException; -import briefing.scrap.api.ScrapConverter; import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.application.strategy.helper.ScrapCreationHelper; +import briefing.scrap.application.strategy.helper.ScrapDeletionHelper; import briefing.scrap.domain.Scrap; import briefing.scrap.domain.repository.ScrapRepository; import briefing.scrap.exception.ScrapException; @@ -22,30 +17,14 @@ @RequiredArgsConstructor public class ScrapV1CommandStrategy implements ScrapCommandStrategy { + private final ScrapCreationHelper scrapCreationHelper; + private final ScrapDeletionHelper scrapDeletionHelper; private final ScrapRepository scrapRepository; - private final MemberRepository memberRepository; - private final BriefingRepository briefingRepository; @Override public Scrap create(ScrapRequest.CreateDTO request) { - // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ - if (scrapRepository.existsByMember_IdAndBriefing_Id( - request.getMemberId(), request.getBriefingId())) - throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); + Scrap scrap = scrapCreationHelper.createScrap(request); - Member member = - memberRepository - .findById(request.getMemberId()) - .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); - - Briefing briefing = - briefingRepository - .findById(request.getBriefingId()) - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); - - Scrap scrap = ScrapConverter.toScrap(member, briefing); - - // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ try { // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ return scrapRepository.save(scrap); @@ -57,12 +36,7 @@ public Scrap create(ScrapRequest.CreateDTO request) { @Override public Scrap delete(Long briefingId, Long memberId) { - Scrap scrap = - scrapRepository - .findByBriefing_IdAndMember_Id(briefingId, memberId) - .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); - scrapRepository.delete(scrap); - return scrap; + return scrapDeletionHelper.deleteScrap(briefingId, memberId); } @Override diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java index 439d5de..90cb772 100644 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java +++ b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java @@ -3,16 +3,12 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.repository.BriefingRepository; import briefing.common.enums.APIVersion; import briefing.exception.ErrorCode; -import briefing.exception.handler.BriefingException; -import briefing.member.domain.Member; -import briefing.member.domain.repository.MemberRepository; -import briefing.member.exception.MemberException; -import briefing.scrap.api.ScrapConverter; import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.application.dto.ScrapV2; +import briefing.scrap.application.strategy.helper.ScrapCreationHelper; +import briefing.scrap.application.strategy.helper.ScrapDeletionHelper; import briefing.scrap.domain.Scrap; import briefing.scrap.domain.repository.ScrapRepository; import briefing.scrap.exception.ScrapException; @@ -22,33 +18,19 @@ @RequiredArgsConstructor public class ScrapV2CommandStrategy implements ScrapCommandStrategy { + private final ScrapCreationHelper scrapCreationHelper; + private final ScrapDeletionHelper scrapDeletionHelper; private final ScrapRepository scrapRepository; - private final MemberRepository memberRepository; - private final BriefingRepository briefingRepository; @Override public Scrap create(ScrapRequest.CreateDTO request) { - // ์ด๋ฏธ ์Šคํฌ๋žฉํ•œ๊ฒฝ์šฐ - if (scrapRepository.existsByMember_IdAndBriefing_Id( - request.getMemberId(), request.getBriefingId())) - throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); + Scrap scrap = scrapCreationHelper.createScrap(request); - Member member = - memberRepository - .findById(request.getMemberId()) - .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); - - Briefing briefing = - briefingRepository - .findById(request.getBriefingId()) - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); - - Scrap scrap = ScrapConverter.toScrap(member, briefing); - - // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ try { // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ - return scrapRepository.save(scrap); + Scrap savedScrap = scrapRepository.save(scrap); + Integer scrapCount = scrapRepository.countByBriefing_Id(request.getBriefingId()); + return ScrapV2.of(savedScrap, scrapCount, Boolean.TRUE); } catch (DataIntegrityViolationException e) { // ์ค‘๋ณต ์Šคํฌ๋žฉ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); @@ -57,12 +39,9 @@ public Scrap create(ScrapRequest.CreateDTO request) { @Override public Scrap delete(Long briefingId, Long memberId) { - Scrap scrap = - scrapRepository - .findByBriefing_IdAndMember_Id(briefingId, memberId) - .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); - scrapRepository.delete(scrap); - return scrap; + Scrap scrap = scrapDeletionHelper.deleteScrap(briefingId, memberId); + Integer scrapCount = scrapRepository.countByBriefing_Id(briefingId); + return ScrapV2.of(scrap, scrapCount, Boolean.FALSE); } @Override diff --git a/src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java b/src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java new file mode 100644 index 0000000..1b20cb0 --- /dev/null +++ b/src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java @@ -0,0 +1,43 @@ +package briefing.scrap.application.strategy.helper; + +import org.springframework.stereotype.Component; + +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.repository.BriefingRepository; +import briefing.exception.ErrorCode; +import briefing.exception.handler.BriefingException; +import briefing.member.domain.Member; +import briefing.member.domain.repository.MemberRepository; +import briefing.member.exception.MemberException; +import briefing.scrap.api.ScrapConverter; +import briefing.scrap.application.dto.ScrapRequest; +import briefing.scrap.domain.Scrap; +import briefing.scrap.domain.repository.ScrapRepository; +import briefing.scrap.exception.ScrapException; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class ScrapCreationHelper { + private final ScrapRepository scrapRepository; + private final MemberRepository memberRepository; + private final BriefingRepository briefingRepository; + + public Scrap createScrap(ScrapRequest.CreateDTO request) { + if (scrapRepository.existsByMember_IdAndBriefing_Id( + request.getMemberId(), request.getBriefingId())) + throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); + + Member member = + memberRepository + .findById(request.getMemberId()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + + Briefing briefing = + briefingRepository + .findById(request.getBriefingId()) + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + + return ScrapConverter.toScrap(member, briefing); + } +} diff --git a/src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java b/src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java new file mode 100644 index 0000000..729d549 --- /dev/null +++ b/src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java @@ -0,0 +1,24 @@ +package briefing.scrap.application.strategy.helper; + +import org.springframework.stereotype.Component; + +import briefing.exception.ErrorCode; +import briefing.scrap.domain.Scrap; +import briefing.scrap.domain.repository.ScrapRepository; +import briefing.scrap.exception.ScrapException; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class ScrapDeletionHelper { + private final ScrapRepository scrapRepository; + + public Scrap deleteScrap(Long briefingId, Long memberId) { + Scrap scrap = + scrapRepository + .findByBriefing_IdAndMember_Id(briefingId, memberId) + .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); + scrapRepository.delete(scrap); + return scrap; + } +} diff --git a/src/main/java/briefing/scrap/domain/repository/ScrapCustomRepository.java b/src/main/java/briefing/scrap/domain/repository/ScrapCustomRepository.java new file mode 100644 index 0000000..a764200 --- /dev/null +++ b/src/main/java/briefing/scrap/domain/repository/ScrapCustomRepository.java @@ -0,0 +1,3 @@ +package briefing.scrap.domain.repository; + +public interface ScrapCustomRepository {} diff --git a/src/main/java/briefing/scrap/domain/repository/ScrapCustomRepositoryImpl.java b/src/main/java/briefing/scrap/domain/repository/ScrapCustomRepositoryImpl.java new file mode 100644 index 0000000..b8f0a48 --- /dev/null +++ b/src/main/java/briefing/scrap/domain/repository/ScrapCustomRepositoryImpl.java @@ -0,0 +1,6 @@ +package briefing.scrap.domain.repository; + +import org.springframework.stereotype.Repository; + +@Repository +public class ScrapCustomRepositoryImpl implements ScrapCustomRepository {} diff --git a/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java b/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java index 45bf2cd..2616b2c 100644 --- a/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java +++ b/src/main/java/briefing/scrap/domain/repository/ScrapRepository.java @@ -7,11 +7,13 @@ import briefing.scrap.domain.Scrap; -public interface ScrapRepository extends JpaRepository { +public interface ScrapRepository extends JpaRepository, ScrapCustomRepository { Optional findByBriefing_IdAndMember_Id(Long briefingId, Long memberId); boolean existsByMember_IdAndBriefing_Id(Long memberId, Long briefingId); List findByMember_Id(Long memberId); + + Integer countByBriefing_Id(Long briefingId); } From 03d50aac393532786e241fb83c38ac3dcf371333 Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Wed, 3 Jan 2024 17:04:27 +0900 Subject: [PATCH 14/28] =?UTF-8?q?:recycle:=20Refactor:=20Scrap=20API=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#138)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/briefing/BriefingApplication.java | 14 ++--- .../java/briefing/config/SwaggerConfig.java | 14 ++--- .../java/briefing/scrap/api/ScrapApi.java | 11 ++-- .../briefing/scrap/api/ScrapConverter.java | 13 +++-- .../java/briefing/scrap/api/ScrapV2Api.java | 18 +++---- .../application/ScrapCommandService.java | 54 ++++++++++++++----- .../scrap/application/ScrapQueryService.java | 4 ++ .../context/ScrapCommandContext.java | 19 ------- .../context/ScrapCommandContextFactory.java | 34 ------------ .../scrap/application/dto/ScrapRequest.java | 3 ++ .../scrap/application/dto/ScrapResponse.java | 6 +-- .../scrap/application/dto/ScrapV2.java | 26 --------- .../strategy/ScrapCommandStrategy.java | 13 ----- .../strategy/ScrapV1CommandStrategy.java | 46 ---------------- .../strategy/ScrapV2CommandStrategy.java | 51 ------------------ .../strategy/helper/ScrapCreationHelper.java | 43 --------------- .../strategy/helper/ScrapDeletionHelper.java | 24 --------- .../security/config/SecurityConfig.java | 43 ++++++++------- .../handler/SwaggerLoginSuccessHandler.java | 9 ++-- 19 files changed, 111 insertions(+), 334 deletions(-) delete mode 100644 src/main/java/briefing/scrap/application/context/ScrapCommandContext.java delete mode 100644 src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java delete mode 100644 src/main/java/briefing/scrap/application/dto/ScrapV2.java delete mode 100644 src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java delete mode 100644 src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java delete mode 100644 src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java delete mode 100644 src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java delete mode 100644 src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java diff --git a/src/main/java/briefing/BriefingApplication.java b/src/main/java/briefing/BriefingApplication.java index c8dc092..747051b 100644 --- a/src/main/java/briefing/BriefingApplication.java +++ b/src/main/java/briefing/BriefingApplication.java @@ -1,7 +1,5 @@ package briefing; -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.servers.Server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -9,11 +7,15 @@ import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; -@OpenAPIDefinition(servers = {@Server(url = "http://localhost:8080", description = "local server"), - @Server(url = "https://dev.briefing.store", description = "dev server"), - @Server(url = "https://api.newsbreifing.store", description = "release server") -}) +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.servers.Server; +@OpenAPIDefinition( + servers = { + @Server(url = "http://localhost:8080", description = "local server"), + @Server(url = "https://dev.briefing.store", description = "dev server"), + @Server(url = "https://api.newsbreifing.store", description = "release server") + }) @SpringBootApplication @EnableFeignClients @EnableRedisRepositories diff --git a/src/main/java/briefing/config/SwaggerConfig.java b/src/main/java/briefing/config/SwaggerConfig.java index e7162f2..cc9d7d5 100644 --- a/src/main/java/briefing/config/SwaggerConfig.java +++ b/src/main/java/briefing/config/SwaggerConfig.java @@ -10,8 +10,6 @@ import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; -import org.springframework.core.annotation.Order; -import springfox.documentation.builders.PathSelectors; @Configuration public class SwaggerConfig { @@ -19,20 +17,14 @@ public class SwaggerConfig { @Bean public GroupedOpenApi v1Api() { - return GroupedOpenApi.builder() - .group("Briefing V1 API") - .pathsToExclude("/v2/**") - .build(); + return GroupedOpenApi.builder().group("Briefing V1 API").pathsToExclude("/v2/**").build(); } @Bean public GroupedOpenApi v2Api() { - String[] paths = { "/v2/**" }; + String[] paths = {"/v2/**"}; - return GroupedOpenApi.builder() - .group("Briefing V2 API") - .pathsToMatch(paths) - .build(); + return GroupedOpenApi.builder().group("Briefing V2 API").pathsToMatch(paths).build(); } @Bean diff --git a/src/main/java/briefing/scrap/api/ScrapApi.java b/src/main/java/briefing/scrap/api/ScrapApi.java index e8e16ce..8d3efb0 100644 --- a/src/main/java/briefing/scrap/api/ScrapApi.java +++ b/src/main/java/briefing/scrap/api/ScrapApi.java @@ -4,7 +4,6 @@ import org.springframework.web.bind.annotation.*; -import briefing.common.enums.APIVersion; import briefing.common.response.CommonResponse; import briefing.scrap.application.ScrapCommandService; import briefing.scrap.application.ScrapQueryService; @@ -25,23 +24,23 @@ public class ScrapApi { @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V1", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/scraps/briefings") public CommonResponse create( - @RequestBody ScrapRequest.CreateDTO request) { - Scrap createdScrap = scrapCommandService.create(request, APIVersion.V1); + @RequestBody final ScrapRequest.CreateDTO request) { + Scrap createdScrap = scrapCommandService.create(request); return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap)); } @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V1", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse delete( - @PathVariable Long briefingId, @PathVariable Long memberId) { - Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId, APIVersion.V1); + @PathVariable final Long briefingId, @PathVariable final Long memberId) { + Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap)); } @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V1", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/scraps/briefings/members/{memberId}") public CommonResponse> getScrapsByMember( - @PathVariable Long memberId) { + @PathVariable final Long memberId) { List scraps = scrapQueryService.getScrapsByMemberId(memberId); return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTO).toList()); } diff --git a/src/main/java/briefing/scrap/api/ScrapConverter.java b/src/main/java/briefing/scrap/api/ScrapConverter.java index 37c43c8..2f88377 100644 --- a/src/main/java/briefing/scrap/api/ScrapConverter.java +++ b/src/main/java/briefing/scrap/api/ScrapConverter.java @@ -5,7 +5,6 @@ import briefing.briefing.domain.Briefing; import briefing.member.domain.Member; import briefing.scrap.application.dto.ScrapResponse; -import briefing.scrap.application.dto.ScrapV2; import briefing.scrap.domain.Scrap; public class ScrapConverter { @@ -18,13 +17,13 @@ public static ScrapResponse.CreateDTO toCreateDTO(Scrap createdScrap) { .build(); } - public static ScrapResponse.CreateDTOV2 toCreateDTOV2(ScrapV2 createdScrap) { + public static ScrapResponse.CreateDTOV2 toCreateDTOV2(Scrap createdScrap, Integer scrapCount) { return ScrapResponse.CreateDTOV2.builder() .scrapId(createdScrap.getId()) .memberId(createdScrap.getMember().getId()) .briefingId(createdScrap.getBriefing().getId()) - .scrapCount(createdScrap.getScrapCount()) - .isScrap(createdScrap.getIsScrap()) + .scrapCount(scrapCount) + .isScrap(Boolean.TRUE) .createdAt(createdScrap.getCreatedAt()) .build(); } @@ -40,11 +39,11 @@ public static ScrapResponse.DeleteDTO toDeleteDTO(Scrap deletedScrap) { .build(); } - public static ScrapResponse.DeleteDTOV2 toDeleteDTOV2(ScrapV2 deletedScrap) { + public static ScrapResponse.DeleteDTOV2 toDeleteDTOV2(Scrap deletedScrap, Integer scrapCount) { return ScrapResponse.DeleteDTOV2.builder() .scrapId(deletedScrap.getId()) - .isScrap(deletedScrap.getIsScrap()) - .scrapCount(deletedScrap.getScrapCount()) + .isScrap(Boolean.FALSE) + .scrapCount(scrapCount) .deletedAt(LocalDateTime.now()) .build(); } diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index 695bb14..5e187c9 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -4,13 +4,11 @@ import org.springframework.web.bind.annotation.*; -import briefing.common.enums.APIVersion; import briefing.common.response.CommonResponse; import briefing.scrap.application.ScrapCommandService; import briefing.scrap.application.ScrapQueryService; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.application.dto.ScrapResponse; -import briefing.scrap.application.dto.ScrapV2; import briefing.scrap.domain.Scrap; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -27,23 +25,25 @@ public class ScrapV2Api { @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/scraps/briefings") public CommonResponse createV2( - @RequestBody ScrapRequest.CreateDTO request) { - Scrap createdScrap = scrapCommandService.create(request, APIVersion.V2); - return CommonResponse.onSuccess(ScrapConverter.toCreateDTOV2((ScrapV2) createdScrap)); + @RequestBody final ScrapRequest.CreateDTO request) { + Scrap createdScrap = scrapCommandService.create(request); + Integer scrapCount = scrapQueryService.countByBriefingId(request.getBriefingId()); + return CommonResponse.onSuccess(ScrapConverter.toCreateDTOV2(createdScrap, scrapCount)); } @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse deleteV2( - @PathVariable Long briefingId, @PathVariable Long memberId) { - Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId, APIVersion.V2); - return CommonResponse.onSuccess(ScrapConverter.toDeleteDTOV2((ScrapV2) deletedScrap)); + @PathVariable final Long briefingId, @PathVariable final Long memberId) { + Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId); + Integer scrapCount = scrapQueryService.countByBriefingId(briefingId); + return CommonResponse.onSuccess(ScrapConverter.toDeleteDTOV2(deletedScrap, scrapCount)); } @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/scraps/briefings/members/{memberId}") public CommonResponse> getScrapsByMemberV2( - @PathVariable Long memberId) { + @PathVariable final Long memberId) { List scraps = scrapQueryService.getScrapsByMemberId(memberId); return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTOV2).toList()); } diff --git a/src/main/java/briefing/scrap/application/ScrapCommandService.java b/src/main/java/briefing/scrap/application/ScrapCommandService.java index 4ddaa18..5aa9238 100644 --- a/src/main/java/briefing/scrap/application/ScrapCommandService.java +++ b/src/main/java/briefing/scrap/application/ScrapCommandService.java @@ -1,13 +1,21 @@ package briefing.scrap.application; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import briefing.common.enums.APIVersion; -import briefing.scrap.application.context.ScrapCommandContext; -import briefing.scrap.application.context.ScrapCommandContextFactory; +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.repository.BriefingRepository; +import briefing.exception.ErrorCode; +import briefing.exception.handler.BriefingException; +import briefing.member.domain.Member; +import briefing.member.domain.repository.MemberRepository; +import briefing.member.exception.MemberException; +import briefing.scrap.api.ScrapConverter; import briefing.scrap.application.dto.ScrapRequest; import briefing.scrap.domain.Scrap; +import briefing.scrap.domain.repository.ScrapRepository; +import briefing.scrap.exception.ScrapException; import lombok.RequiredArgsConstructor; @Service @@ -15,17 +23,39 @@ @RequiredArgsConstructor public class ScrapCommandService { - private final ScrapCommandContextFactory scrapCommandContextFactory; + private final ScrapRepository scrapRepository; + private final MemberRepository memberRepository; + private final BriefingRepository briefingRepository; - public Scrap create(ScrapRequest.CreateDTO request, APIVersion version) { - ScrapCommandContext scrapCommandContext = - scrapCommandContextFactory.getContextByVersion(version); - return scrapCommandContext.create(request); + public Scrap create(ScrapRequest.CreateDTO request) { + if (scrapRepository.existsByMember_IdAndBriefing_Id( + request.getMemberId(), request.getBriefingId())) + throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); + + Member member = + memberRepository + .findById(request.getMemberId()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + + Briefing briefing = + briefingRepository + .findById(request.getBriefingId()) + .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + + Scrap scrap = ScrapConverter.toScrap(member, briefing); + try { + return scrapRepository.save(scrap); + } catch (DataIntegrityViolationException e) { + throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); + } } - public Scrap delete(Long briefingId, Long memberId, APIVersion version) { - ScrapCommandContext scrapCommandContext = - scrapCommandContextFactory.getContextByVersion(version); - return scrapCommandContext.delete(briefingId, memberId); + public Scrap delete(Long briefingId, Long memberId) { + Scrap scrap = + scrapRepository + .findByBriefing_IdAndMember_Id(briefingId, memberId) + .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); + scrapRepository.delete(scrap); + return scrap; } } diff --git a/src/main/java/briefing/scrap/application/ScrapQueryService.java b/src/main/java/briefing/scrap/application/ScrapQueryService.java index 6515f54..43800ec 100644 --- a/src/main/java/briefing/scrap/application/ScrapQueryService.java +++ b/src/main/java/briefing/scrap/application/ScrapQueryService.java @@ -23,4 +23,8 @@ public List getScrapsByMemberId(Long memberId) { public Boolean existsByMemberIdAndBriefingId(Long memberId, Long briefingId) { return scrapRepository.existsByMember_IdAndBriefing_Id(memberId, briefingId); } + + public Integer countByBriefingId(Long briefingId) { + return scrapRepository.countByBriefing_Id(briefingId); + } } diff --git a/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java b/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java deleted file mode 100644 index dc191fe..0000000 --- a/src/main/java/briefing/scrap/application/context/ScrapCommandContext.java +++ /dev/null @@ -1,19 +0,0 @@ -package briefing.scrap.application.context; - -import briefing.scrap.application.dto.ScrapRequest; -import briefing.scrap.application.strategy.ScrapCommandStrategy; -import briefing.scrap.domain.Scrap; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class ScrapCommandContext { - private final ScrapCommandStrategy scrapCommandStrategy; - - public Scrap create(ScrapRequest.CreateDTO request) { - return scrapCommandStrategy.create(request); - } - - public Scrap delete(Long briefingId, Long memberId) { - return scrapCommandStrategy.delete(briefingId, memberId); - } -} diff --git a/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java b/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java deleted file mode 100644 index d2e516d..0000000 --- a/src/main/java/briefing/scrap/application/context/ScrapCommandContextFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package briefing.scrap.application.context; - -import java.util.EnumMap; -import java.util.List; -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import briefing.common.enums.APIVersion; -import briefing.scrap.application.strategy.ScrapCommandStrategy; - -@Component -public class ScrapCommandContextFactory { - - private final Map contextMap; - - @Autowired - public ScrapCommandContextFactory(List strategies) { - contextMap = new EnumMap<>(APIVersion.class); - for (ScrapCommandStrategy strategy : strategies) { - APIVersion version = strategy.getVersion(); - contextMap.put(version, new ScrapCommandContext(strategy)); - } - } - - public ScrapCommandContext getContextByVersion(APIVersion version) { - ScrapCommandContext context = contextMap.get(version); - if (context == null) { - throw new IllegalArgumentException("Invalid API version: " + version); - } - return context; - } -} diff --git a/src/main/java/briefing/scrap/application/dto/ScrapRequest.java b/src/main/java/briefing/scrap/application/dto/ScrapRequest.java index cf4d489..5c6705a 100644 --- a/src/main/java/briefing/scrap/application/dto/ScrapRequest.java +++ b/src/main/java/briefing/scrap/application/dto/ScrapRequest.java @@ -1,7 +1,10 @@ package briefing.scrap.application.dto; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ScrapRequest { @Getter diff --git a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java index b5d6fb3..5875ee0 100644 --- a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java +++ b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java @@ -5,11 +5,9 @@ import briefing.briefing.domain.TimeOfDay; import briefing.chatting.domain.GptModel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ScrapResponse { @Builder diff --git a/src/main/java/briefing/scrap/application/dto/ScrapV2.java b/src/main/java/briefing/scrap/application/dto/ScrapV2.java deleted file mode 100644 index a33fa72..0000000 --- a/src/main/java/briefing/scrap/application/dto/ScrapV2.java +++ /dev/null @@ -1,26 +0,0 @@ -package briefing.scrap.application.dto; - -import java.time.LocalDateTime; - -import briefing.scrap.domain.Scrap; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ScrapV2 extends Scrap { - private Integer scrapCount; - private Boolean isScrap; - private LocalDateTime createdAt; - - private ScrapV2(Scrap scrap, Integer scrapCount, Boolean isScrap) { - super(scrap.getId(), scrap.getMember(), scrap.getBriefing()); - this.scrapCount = scrapCount; - this.isScrap = isScrap; - this.createdAt = scrap.getCreatedAt(); - } - - public static ScrapV2 of(Scrap scrap, Integer scrapCount, Boolean isScrap) { - return new ScrapV2(scrap, scrapCount, isScrap); - } -} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java deleted file mode 100644 index 80baba1..0000000 --- a/src/main/java/briefing/scrap/application/strategy/ScrapCommandStrategy.java +++ /dev/null @@ -1,13 +0,0 @@ -package briefing.scrap.application.strategy; - -import briefing.common.enums.APIVersion; -import briefing.scrap.application.dto.ScrapRequest; -import briefing.scrap.domain.Scrap; - -public interface ScrapCommandStrategy { - Scrap create(ScrapRequest.CreateDTO request); - - Scrap delete(Long briefingId, Long memberId); - - APIVersion getVersion(); -} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java deleted file mode 100644 index cb8ea7c..0000000 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV1CommandStrategy.java +++ /dev/null @@ -1,46 +0,0 @@ -package briefing.scrap.application.strategy; - -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.stereotype.Component; - -import briefing.common.enums.APIVersion; -import briefing.exception.ErrorCode; -import briefing.scrap.application.dto.ScrapRequest; -import briefing.scrap.application.strategy.helper.ScrapCreationHelper; -import briefing.scrap.application.strategy.helper.ScrapDeletionHelper; -import briefing.scrap.domain.Scrap; -import briefing.scrap.domain.repository.ScrapRepository; -import briefing.scrap.exception.ScrapException; -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class ScrapV1CommandStrategy implements ScrapCommandStrategy { - - private final ScrapCreationHelper scrapCreationHelper; - private final ScrapDeletionHelper scrapDeletionHelper; - private final ScrapRepository scrapRepository; - - @Override - public Scrap create(ScrapRequest.CreateDTO request) { - Scrap scrap = scrapCreationHelper.createScrap(request); - - try { - // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ - return scrapRepository.save(scrap); - } catch (DataIntegrityViolationException e) { - // ์ค‘๋ณต ์Šคํฌ๋žฉ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ - throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); - } - } - - @Override - public Scrap delete(Long briefingId, Long memberId) { - return scrapDeletionHelper.deleteScrap(briefingId, memberId); - } - - @Override - public APIVersion getVersion() { - return APIVersion.V1; - } -} diff --git a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java b/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java deleted file mode 100644 index 90cb772..0000000 --- a/src/main/java/briefing/scrap/application/strategy/ScrapV2CommandStrategy.java +++ /dev/null @@ -1,51 +0,0 @@ -package briefing.scrap.application.strategy; - -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.stereotype.Component; - -import briefing.common.enums.APIVersion; -import briefing.exception.ErrorCode; -import briefing.scrap.application.dto.ScrapRequest; -import briefing.scrap.application.dto.ScrapV2; -import briefing.scrap.application.strategy.helper.ScrapCreationHelper; -import briefing.scrap.application.strategy.helper.ScrapDeletionHelper; -import briefing.scrap.domain.Scrap; -import briefing.scrap.domain.repository.ScrapRepository; -import briefing.scrap.exception.ScrapException; -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class ScrapV2CommandStrategy implements ScrapCommandStrategy { - - private final ScrapCreationHelper scrapCreationHelper; - private final ScrapDeletionHelper scrapDeletionHelper; - private final ScrapRepository scrapRepository; - - @Override - public Scrap create(ScrapRequest.CreateDTO request) { - Scrap scrap = scrapCreationHelper.createScrap(request); - - try { - // Scrap ์—”ํ‹ฐํ‹ฐ ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ - Scrap savedScrap = scrapRepository.save(scrap); - Integer scrapCount = scrapRepository.countByBriefing_Id(request.getBriefingId()); - return ScrapV2.of(savedScrap, scrapCount, Boolean.TRUE); - } catch (DataIntegrityViolationException e) { - // ์ค‘๋ณต ์Šคํฌ๋žฉ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ - throw new ScrapException(ErrorCode.DUPLICATE_SCRAP); - } - } - - @Override - public Scrap delete(Long briefingId, Long memberId) { - Scrap scrap = scrapDeletionHelper.deleteScrap(briefingId, memberId); - Integer scrapCount = scrapRepository.countByBriefing_Id(briefingId); - return ScrapV2.of(scrap, scrapCount, Boolean.FALSE); - } - - @Override - public APIVersion getVersion() { - return APIVersion.V2; - } -} diff --git a/src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java b/src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java deleted file mode 100644 index 1b20cb0..0000000 --- a/src/main/java/briefing/scrap/application/strategy/helper/ScrapCreationHelper.java +++ /dev/null @@ -1,43 +0,0 @@ -package briefing.scrap.application.strategy.helper; - -import org.springframework.stereotype.Component; - -import briefing.briefing.domain.Briefing; -import briefing.briefing.domain.repository.BriefingRepository; -import briefing.exception.ErrorCode; -import briefing.exception.handler.BriefingException; -import briefing.member.domain.Member; -import briefing.member.domain.repository.MemberRepository; -import briefing.member.exception.MemberException; -import briefing.scrap.api.ScrapConverter; -import briefing.scrap.application.dto.ScrapRequest; -import briefing.scrap.domain.Scrap; -import briefing.scrap.domain.repository.ScrapRepository; -import briefing.scrap.exception.ScrapException; -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class ScrapCreationHelper { - private final ScrapRepository scrapRepository; - private final MemberRepository memberRepository; - private final BriefingRepository briefingRepository; - - public Scrap createScrap(ScrapRequest.CreateDTO request) { - if (scrapRepository.existsByMember_IdAndBriefing_Id( - request.getMemberId(), request.getBriefingId())) - throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS); - - Member member = - memberRepository - .findById(request.getMemberId()) - .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); - - Briefing briefing = - briefingRepository - .findById(request.getBriefingId()) - .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); - - return ScrapConverter.toScrap(member, briefing); - } -} diff --git a/src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java b/src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java deleted file mode 100644 index 729d549..0000000 --- a/src/main/java/briefing/scrap/application/strategy/helper/ScrapDeletionHelper.java +++ /dev/null @@ -1,24 +0,0 @@ -package briefing.scrap.application.strategy.helper; - -import org.springframework.stereotype.Component; - -import briefing.exception.ErrorCode; -import briefing.scrap.domain.Scrap; -import briefing.scrap.domain.repository.ScrapRepository; -import briefing.scrap.exception.ScrapException; -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class ScrapDeletionHelper { - private final ScrapRepository scrapRepository; - - public Scrap deleteScrap(Long briefingId, Long memberId) { - Scrap scrap = - scrapRepository - .findByBriefing_IdAndMember_Id(briefingId, memberId) - .orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND)); - scrapRepository.delete(scrap); - return scrap; - } -} diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java index 0b28120..cf2b06c 100644 --- a/src/main/java/briefing/security/config/SecurityConfig.java +++ b/src/main/java/briefing/security/config/SecurityConfig.java @@ -4,7 +4,6 @@ import java.util.Collections; -import briefing.security.handler.SwaggerLoginSuccessHandler; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,18 +11,15 @@ import org.springframework.http.HttpMethod; import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @@ -34,6 +30,7 @@ import briefing.security.handler.JwtAccessDeniedHandler; import briefing.security.handler.JwtAuthenticationEntryPoint; import briefing.security.handler.JwtAuthenticationExceptionHandler; +import briefing.security.handler.SwaggerLoginSuccessHandler; import briefing.security.provider.TokenProvider; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -56,7 +53,8 @@ public class SecurityConfig { private static final String[] WHITE_LIST = {}; - private final SwaggerLoginSuccessHandler swaggerLoginSuccessHandler = new SwaggerLoginSuccessHandler(); + private final SwaggerLoginSuccessHandler swaggerLoginSuccessHandler = + new SwaggerLoginSuccessHandler(); @Value("${swagger.login.id}") private String swaggerId; @@ -96,16 +94,20 @@ public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Except .httpBasic(withDefaults()) .csrf(AbstractHttpConfigurer::disable) // ๋น„ํ™œ์„ฑํ™” .sessionManagement( - manage -> - manage.sessionCreationPolicy( - SessionCreationPolicy.IF_REQUIRED) - ) - .formLogin(authorize->authorize - .successHandler(swaggerLoginSuccessHandler) - .defaultSuccessUrl("/swagger-ui/index.html") - .permitAll()) - .authorizeHttpRequests(authorize-> authorize.requestMatchers("/swagger-ui/index.html").authenticated() - .anyRequest().permitAll()) + manage -> manage.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) + .formLogin( + authorize -> + authorize + .successHandler(swaggerLoginSuccessHandler) + .defaultSuccessUrl("/swagger-ui/index.html") + .permitAll()) + .authorizeHttpRequests( + authorize -> + authorize + .requestMatchers("/swagger-ui/index.html") + .authenticated() + .anyRequest() + .permitAll()) .build(); } @@ -152,11 +154,12 @@ public SecurityFilterChain JwtFilterChain(HttpSecurity http) throws Exception { @Bean public UserDetailsService userDetailsService() { - UserDetails userDetails = User.builder() - .username(swaggerId) - .password(passwordEncoder().encode(swaggerPass)) - .roles("USER", "ADMIN") - .build(); + UserDetails userDetails = + User.builder() + .username(swaggerId) + .password(passwordEncoder().encode(swaggerPass)) + .roles("USER", "ADMIN") + .build(); return new InMemoryUserDetailsManager(userDetails); } diff --git a/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java b/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java index c13d177..39aa9a2 100644 --- a/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java +++ b/src/main/java/briefing/security/handler/SwaggerLoginSuccessHandler.java @@ -1,17 +1,20 @@ package briefing.security.handler; +import java.io.IOException; + import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import java.io.IOException; - public class SwaggerLoginSuccessHandler implements AuthenticationSuccessHandler { @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { request.getSession().setMaxInactiveInterval(120); } } From ae2bc34da5caff4156f37ec27e85ba41d8cac010 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Fri, 5 Jan 2024 02:58:31 +0900 Subject: [PATCH 15/28] =?UTF-8?q?:memo:=20Docs=20:=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=202024.01.05?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 033b49d..cc1aeb0 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@
Spring Cloud -- write +- Node.js ํฌ๋กค๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ํ•˜๋‚˜์˜ Base Url๋งŒ์„ ์ด์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ ์„œ๋ฒ„์™€ ๋ฆด๋ฆฌ์ฆˆ ์„œ๋ฒ„ ๋ชจ๋‘์—๊ฒŒ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์„œ ํŽธ์˜์„ฑ์„ ๋Š˜๋ ธ์Šต๋‹ˆ๋‹ค. +- ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์„ Feign ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœ ํ–ˆ์Šต๋‹ˆ๋‹ค. QueryDSL - ์ปดํŒŒ์ผ ์‹œ์  ๋ฌธ๋ฒ• ๊ฒ€์‚ฌ์™€ ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด QueryDSL์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. @@ -79,6 +80,7 @@ ## ๐Ÿ“š ๊ฐœ๋ฐœ ๊ณผ์ • - [[Briefing] API ๋ฒ„์ „ ๊ด€๋ฆฌ & ์ „๋žต ํŒจํ„ด](https://poisson-it.tistory.com/75) - [[Briefing] Spotless๋กœ ์ฝ”๋“œ ํฌ๋งท ์œ ์ง€ํ•˜๊ธฐ](https://poisson-it.tistory.com/77) +- [[Briefing] Spring Security - Swagger ๋กœ๊ทธ์ธ ์ ์šฉํ•˜๊ธฐ](https://ddol-dev-blog.tistory.com/3)
From 5283358d33c4e5ad41685eef77e66184ab497371 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Fri, 5 Jan 2024 02:59:54 +0900 Subject: [PATCH 16/28] =?UTF-8?q?:memo:=20Docs=20:=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95=202024.01.0?= =?UTF-8?q?5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc1aeb0..275fb8c 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Spring Cloud - Node.js ํฌ๋กค๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ํ•˜๋‚˜์˜ Base Url๋งŒ์„ ์ด์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ ์„œ๋ฒ„์™€ ๋ฆด๋ฆฌ์ฆˆ ์„œ๋ฒ„ ๋ชจ๋‘์—๊ฒŒ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์„œ ํŽธ์˜์„ฑ์„ ๋Š˜๋ ธ์Šต๋‹ˆ๋‹ค. -- ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์„ Feign ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœ ํ–ˆ์Šต๋‹ˆ๋‹ค. +- ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด Feign ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœ ํ–ˆ์Šต๋‹ˆ๋‹ค. QueryDSL - ์ปดํŒŒ์ผ ์‹œ์  ๋ฌธ๋ฒ• ๊ฒ€์‚ฌ์™€ ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด QueryDSL์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. From 7a1afa94a641d13251e7bf0c1e02596845b12bdf Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Fri, 5 Jan 2024 10:50:59 +0900 Subject: [PATCH 17/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 275fb8c..292ff98 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@
- +

From a1dee9e9a22bd85199535b776c1e7d9a99fe2103 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Fri, 5 Jan 2024 14:00:40 +0900 Subject: [PATCH 18/28] =?UTF-8?q?:sparkles:=20Feat:=20API=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=BA=90=EC=8B=B1=EC=9D=84=20=ED=86=B5=ED=95=9C=20?= =?UTF-8?q?=EB=B8=8C=EB=A6=AC=ED=95=91=20V2=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/briefing/BriefingApplication.java | 2 + .../briefing/briefing/api/BriefingV2Api.java | 2 + .../application/dto/BriefingRequestParam.java | 1 + .../common/response/CommonResponse.java | 14 ++++-- .../java/briefing/config/CacheConfig.java | 48 +++++++++++++++++++ .../java/briefing/scrap/api/ScrapV2Api.java | 3 ++ 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/main/java/briefing/config/CacheConfig.java diff --git a/src/main/java/briefing/BriefingApplication.java b/src/main/java/briefing/BriefingApplication.java index 747051b..186981a 100644 --- a/src/main/java/briefing/BriefingApplication.java +++ b/src/main/java/briefing/BriefingApplication.java @@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; @@ -17,6 +18,7 @@ @Server(url = "https://api.newsbreifing.store", description = "release server") }) @SpringBootApplication +@EnableCaching @EnableFeignClients @EnableRedisRepositories @ImportAutoConfiguration({FeignAutoConfiguration.class}) diff --git a/src/main/java/briefing/briefing/api/BriefingV2Api.java b/src/main/java/briefing/briefing/api/BriefingV2Api.java index a6a80ce..9b18e2a 100644 --- a/src/main/java/briefing/briefing/api/BriefingV2Api.java +++ b/src/main/java/briefing/briefing/api/BriefingV2Api.java @@ -4,6 +4,7 @@ import java.util.Optional; import org.springdoc.core.annotations.ParameterObject; +import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.*; import briefing.briefing.application.BriefingCommandService; @@ -32,6 +33,7 @@ public class BriefingV2Api { @GetMapping("/briefings") @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V2", description = "") + @Cacheable(value = "findBriefingsV2", key = "#params.toString()") public CommonResponse findBriefingsV2( @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params) { List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2); diff --git a/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java b/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java index 90a5069..9870e34 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java @@ -16,6 +16,7 @@ public class BriefingRequestParam { @Setter @NoArgsConstructor @AllArgsConstructor + @ToString public static class BriefingPreviewListParam { @NotNull private BriefingType type; private LocalDate date; diff --git a/src/main/java/briefing/common/response/CommonResponse.java b/src/main/java/briefing/common/response/CommonResponse.java index 43b728b..c159356 100644 --- a/src/main/java/briefing/common/response/CommonResponse.java +++ b/src/main/java/briefing/common/response/CommonResponse.java @@ -12,13 +12,19 @@ public class CommonResponse { @JsonProperty("isSuccess") - private final Boolean isSuccess; + private Boolean isSuccess; - private final String code; - private final String message; + @JsonProperty("code") + private String code; + + @JsonProperty("message") + private String message; + + @JsonProperty("result") private T result; - // ์š”์ฒญ์— ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ11 + // ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ์ถ”๊ฐ€ + public CommonResponse() {} public static CommonResponse onSuccess(T data) { return new CommonResponse<>(true, "์š”์ฒญ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.", "1000", data); diff --git a/src/main/java/briefing/config/CacheConfig.java b/src/main/java/briefing/config/CacheConfig.java new file mode 100644 index 0000000..68ef588 --- /dev/null +++ b/src/main/java/briefing/config/CacheConfig.java @@ -0,0 +1,48 @@ +package briefing.config; + +import java.time.Duration; + +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +@Configuration +public class CacheConfig { + + @Bean + public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { + PolymorphicTypeValidator typeValidator = + BasicPolymorphicTypeValidator.builder().allowIfSubType(Object.class).build(); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL); + + RedisCacheConfiguration cacheConfiguration = + RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofHours(1)) // ์˜ˆ: ์บ์‹œ ์œ ํšจ ์‹œ๊ฐ„ 1์‹œ๊ฐ„ + .disableCachingNullValues() + .prefixCacheNameWith("responseCache::") // ์บ์‹œ ํ‚ค ์ ‘๋‘์‚ฌ ์„ค์ • + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer(objectMapper))); + + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(cacheConfiguration) + .build(); + } +} diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index 5e187c9..c97d31f 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -2,6 +2,7 @@ import java.util.List; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.web.bind.annotation.*; import briefing.common.response.CommonResponse; @@ -22,6 +23,7 @@ public class ScrapV2Api { private final ScrapQueryService scrapQueryService; private final ScrapCommandService scrapCommandService; + @CacheEvict(value = "findBriefingsV2", allEntries = true) @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/scraps/briefings") public CommonResponse createV2( @@ -40,6 +42,7 @@ public CommonResponse deleteV2( return CommonResponse.onSuccess(ScrapConverter.toDeleteDTOV2(deletedScrap, scrapCount)); } + @CacheEvict(value = "findBriefingsV2", allEntries = true) @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/scraps/briefings/members/{memberId}") public CommonResponse> getScrapsByMemberV2( From ba85b24b41bb8ec3e0e6aeacc330f98aae1a1168 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Fri, 5 Jan 2024 14:43:11 +0900 Subject: [PATCH 19/28] =?UTF-8?q?:sparkles:=20Feat:=20=EC=BA=90=EC=8B=9C?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EC=8B=9C=EA=B0=84=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/briefing/config/CacheConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/briefing/config/CacheConfig.java b/src/main/java/briefing/config/CacheConfig.java index 68ef588..a368f66 100644 --- a/src/main/java/briefing/config/CacheConfig.java +++ b/src/main/java/briefing/config/CacheConfig.java @@ -31,7 +31,7 @@ public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() - .entryTtl(Duration.ofHours(1)) // ์˜ˆ: ์บ์‹œ ์œ ํšจ ์‹œ๊ฐ„ 1์‹œ๊ฐ„ + .entryTtl(Duration.ofMinutes(30)) // ์บ์‹œ ์œ ํšจ ์‹œ๊ฐ„ 30๋ถ„ .disableCachingNullValues() .prefixCacheNameWith("responseCache::") // ์บ์‹œ ํ‚ค ์ ‘๋‘์‚ฌ ์„ค์ • .serializeKeysWith( From 7fc920024311a3361e927097de5ead7f292e9250 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Fri, 5 Jan 2024 16:07:42 +0900 Subject: [PATCH 20/28] =?UTF-8?q?:pencil2:=20Fix:=20@CacheEvict=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/briefing/scrap/api/ScrapV2Api.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index c97d31f..41c3a16 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -33,6 +33,7 @@ public CommonResponse createV2( return CommonResponse.onSuccess(ScrapConverter.toCreateDTOV2(createdScrap, scrapCount)); } + @CacheEvict(value = "findBriefingsV2", allEntries = true) @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse deleteV2( @@ -42,7 +43,6 @@ public CommonResponse deleteV2( return CommonResponse.onSuccess(ScrapConverter.toDeleteDTOV2(deletedScrap, scrapCount)); } - @CacheEvict(value = "findBriefingsV2", allEntries = true) @Operation(summary = "05-03 Scrap๐Ÿ“ ๋‚ด ์Šคํฌ๋žฉ ์กฐํšŒ V2", description = "๋‚ด ์Šคํฌ๋žฉ์„ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @GetMapping("/scraps/briefings/members/{memberId}") public CommonResponse> getScrapsByMemberV2( From 08392fb01435caab4f7bf05bf1266003b380eccc Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sat, 6 Jan 2024 01:30:56 +0900 Subject: [PATCH 21/28] =?UTF-8?q?:pencil2:=20Fix:=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=ED=82=A4=20=EB=B3=80=EA=B2=BD=20&=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=BA=90=EC=8B=9C=20Evict=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../briefing/briefing/api/BriefingV2Api.java | 2 +- .../annotation/CacheEvictByBriefingId.java | 14 ++++ .../aspect/CacheEvictByBriefingIdAspect.java | 66 +++++++++++++++++++ .../java/briefing/scrap/api/ScrapV2Api.java | 6 +- 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/main/java/briefing/common/aop/annotation/CacheEvictByBriefingId.java create mode 100644 src/main/java/briefing/common/aop/aspect/CacheEvictByBriefingIdAspect.java diff --git a/src/main/java/briefing/briefing/api/BriefingV2Api.java b/src/main/java/briefing/briefing/api/BriefingV2Api.java index 9b18e2a..b887e5f 100644 --- a/src/main/java/briefing/briefing/api/BriefingV2Api.java +++ b/src/main/java/briefing/briefing/api/BriefingV2Api.java @@ -33,7 +33,7 @@ public class BriefingV2Api { @GetMapping("/briefings") @Operation(summary = "03-01Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋ชฉ๋ก ์กฐํšŒ V2", description = "") - @Cacheable(value = "findBriefingsV2", key = "#params.toString()") + @Cacheable(value = "findBriefingsV2", key = "#params.getType()") public CommonResponse findBriefingsV2( @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params) { List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2); diff --git a/src/main/java/briefing/common/aop/annotation/CacheEvictByBriefingId.java b/src/main/java/briefing/common/aop/annotation/CacheEvictByBriefingId.java new file mode 100644 index 0000000..22d7db4 --- /dev/null +++ b/src/main/java/briefing/common/aop/annotation/CacheEvictByBriefingId.java @@ -0,0 +1,14 @@ +package briefing.common.aop.annotation; + +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 CacheEvictByBriefingId { + String value(); // ์บ์‹œ ์ด๋ฆ„ + + String briefingId(); // ์บ์‹œ ํ‚ค๋ฅผ ์œ„ํ•œ briefingId +} diff --git a/src/main/java/briefing/common/aop/aspect/CacheEvictByBriefingIdAspect.java b/src/main/java/briefing/common/aop/aspect/CacheEvictByBriefingIdAspect.java new file mode 100644 index 0000000..e76350d --- /dev/null +++ b/src/main/java/briefing/common/aop/aspect/CacheEvictByBriefingIdAspect.java @@ -0,0 +1,66 @@ +package briefing.common.aop.aspect; + +import java.util.Optional; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import briefing.briefing.domain.Briefing; +import briefing.briefing.domain.repository.BriefingRepository; +import briefing.common.aop.annotation.CacheEvictByBriefingId; +import lombok.RequiredArgsConstructor; + +@Aspect +@Component +@RequiredArgsConstructor +public class CacheEvictByBriefingIdAspect { + + private final BriefingRepository briefingRepository; + private final CacheManager cacheManager; + private SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); + private StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + + @After(value = "@annotation(cacheEvictByBriefingId)") + public void evictCache(JoinPoint joinPoint, CacheEvictByBriefingId cacheEvictByBriefingId) { + System.out.println("AOP CALL!!!"); + + String briefingIdExpression = cacheEvictByBriefingId.briefingId(); + Long briefingId = evaluateExpression(joinPoint, briefingIdExpression, Long.class); + + Optional optionalBriefing = briefingRepository.findById(briefingId); + + if (optionalBriefing.isPresent()) { + Briefing briefing = optionalBriefing.get(); + String cacheKey = briefing.getType().name(); + Cache cache = cacheManager.getCache(cacheEvictByBriefingId.value()); + System.out.println(cacheEvictByBriefingId.value()); + System.out.println(cacheKey); + System.out.println(cache); + if (cache != null) { + cache.evict(cacheKey); + } + } + } + + private T evaluateExpression( + JoinPoint joinPoint, String expression, Class desiredResultType) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Object[] args = joinPoint.getArgs(); + String[] parameterNames = methodSignature.getParameterNames(); + + for (int i = 0; i < parameterNames.length; i++) { + evaluationContext.setVariable(parameterNames[i], args[i]); + } + + Expression parsedExpression = spelExpressionParser.parseExpression(expression); + return parsedExpression.getValue(evaluationContext, desiredResultType); + } +} diff --git a/src/main/java/briefing/scrap/api/ScrapV2Api.java b/src/main/java/briefing/scrap/api/ScrapV2Api.java index 41c3a16..129807f 100644 --- a/src/main/java/briefing/scrap/api/ScrapV2Api.java +++ b/src/main/java/briefing/scrap/api/ScrapV2Api.java @@ -2,9 +2,9 @@ import java.util.List; -import org.springframework.cache.annotation.CacheEvict; import org.springframework.web.bind.annotation.*; +import briefing.common.aop.annotation.CacheEvictByBriefingId; import briefing.common.response.CommonResponse; import briefing.scrap.application.ScrapCommandService; import briefing.scrap.application.ScrapQueryService; @@ -23,7 +23,7 @@ public class ScrapV2Api { private final ScrapQueryService scrapQueryService; private final ScrapCommandService scrapCommandService; - @CacheEvict(value = "findBriefingsV2", allEntries = true) + @CacheEvictByBriefingId(value = "findBriefingsV2", briefingId = "#request.getBriefingId()") @Operation(summary = "05-01 Scrap๐Ÿ“ ์Šคํฌ๋žฉํ•˜๊ธฐ V2", description = "๋ธŒ๋ฆฌํ•‘์„ ์Šคํฌ๋žฉํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @PostMapping("/scraps/briefings") public CommonResponse createV2( @@ -33,7 +33,7 @@ public CommonResponse createV2( return CommonResponse.onSuccess(ScrapConverter.toCreateDTOV2(createdScrap, scrapCount)); } - @CacheEvict(value = "findBriefingsV2", allEntries = true) + @CacheEvictByBriefingId(value = "findBriefingsV2", briefingId = "#briefingId") @Operation(summary = "05-02 Scrap๐Ÿ“ ์Šคํฌ๋žฉ ์ทจ์†Œ V2", description = "์Šคํฌ๋žฉ์„ ์ทจ์†Œํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.") @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}") public CommonResponse deleteV2( From 40fa22594a41ba3f6be6520285fd17b30ae94e4f Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Sat, 6 Jan 2024 02:33:47 +0900 Subject: [PATCH 22/28] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 292ff98..a775866 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ - [[Briefing] API ๋ฒ„์ „ ๊ด€๋ฆฌ & ์ „๋žต ํŒจํ„ด](https://poisson-it.tistory.com/75) - [[Briefing] Spotless๋กœ ์ฝ”๋“œ ํฌ๋งท ์œ ์ง€ํ•˜๊ธฐ](https://poisson-it.tistory.com/77) - [[Briefing] Spring Security - Swagger ๋กœ๊ทธ์ธ ์ ์šฉํ•˜๊ธฐ](https://ddol-dev-blog.tistory.com/3) +- [[Briefing] API ์‘๋‹ต ์บ์‹ฑ์„ ํ†ตํ•œ ์†๋„ ๊ฐœ์„ ](https://poisson-it.tistory.com/78)
From 9d2c7820563541e72f40e95583fbd962983e4f8e Mon Sep 17 00:00:00 2001 From: SeongHoon Jeong Date: Sun, 7 Jan 2024 13:18:05 +0900 Subject: [PATCH 23/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a775866..43ddd9f 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ - [[Briefing] API ๋ฒ„์ „ ๊ด€๋ฆฌ & ์ „๋žต ํŒจํ„ด](https://poisson-it.tistory.com/75) - [[Briefing] Spotless๋กœ ์ฝ”๋“œ ํฌ๋งท ์œ ์ง€ํ•˜๊ธฐ](https://poisson-it.tistory.com/77) - [[Briefing] Spring Security - Swagger ๋กœ๊ทธ์ธ ์ ์šฉํ•˜๊ธฐ](https://ddol-dev-blog.tistory.com/3) -- [[Briefing] API ์‘๋‹ต ์บ์‹ฑ์„ ํ†ตํ•œ ์†๋„ ๊ฐœ์„ ](https://poisson-it.tistory.com/78) +- [[Briefing] API ์‘๋‹ต ์บ์‹ฑ์„ ํ†ตํ•œ ์กฐํšŒ ์†๋„ ๊ฐœ์„ ](https://poisson-it.tistory.com/78)
From 79da5ff2cbd02ce934f642f383760fa6413d7213 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Sun, 7 Jan 2024 14:13:08 +0900 Subject: [PATCH 24/28] =?UTF-8?q?:sparkles:=20Feat=20:=20=EB=B8=8C?= =?UTF-8?q?=EB=A6=AC=ED=95=91=20=EC=88=98=EC=A0=95=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../briefing/briefing/api/BriefingApi.java | 26 +++++++++++++++++++ .../briefing/api/BriefingConverter.java | 9 +++++++ .../application/BriefingCommandService.java | 19 ++++++++++++++ .../application/dto/BriefingRequestDTO.java | 10 +++++++ .../application/dto/BriefingResponseDTO.java | 10 +++++++ .../briefing/briefing/domain/Briefing.java | 12 +++++++++ 7 files changed, 87 insertions(+) diff --git a/build.gradle b/build.gradle index a209aaa..6945639 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation("org.springframework.boot:spring-boot-starter-oauth2-client") } def querydslSrcDir = 'src/main/generated' diff --git a/src/main/java/briefing/briefing/api/BriefingApi.java b/src/main/java/briefing/briefing/api/BriefingApi.java index 73d43af..f9ac02f 100644 --- a/src/main/java/briefing/briefing/api/BriefingApi.java +++ b/src/main/java/briefing/briefing/api/BriefingApi.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; +import jakarta.validation.Valid; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @@ -69,4 +70,29 @@ public CommonResponse findBriefing( public void createBriefing(@RequestBody final BriefingRequestDTO.BriefingCreate request) { briefingCommandService.createBriefing(request); } + + + /* + * TODO ๋ธŒ๋ฆฌํ•‘ ์ˆ˜์ • API๋Š” ์šฐ์„ ์ ์œผ๋กœ ์ธ๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•˜์ง€ ์•Š์œผ๋‚˜ + * ๋น ๋ฅธ ์‹œ์ผ ๋‚ด๋กœ ๋ธŒ๋ฆฌํ•‘ ๋“ฑ๋ก๊ณผ ํ•จ๊ป˜ ์ธ๊ฐ€ ์ฒ˜๋ฆฌ ์˜ˆ์ • + * ์ฆ‰ ์œ ์ €์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋Š” ์ผ๋ จ์˜ ๊ณผ์ •์— ๋Œ€ํ•œ ๋ฆฌํŒฉํ† ๋ง์ด ํ•„์š”ํ•จ ์ด๋Š” CYY1007์ด ์ง„ํ–‰ํ•˜๊ฒ ์Œ + */ + + /** + * + * @param id, BriefingResponseDTO.BriefingUpdateDTO + * @return ์ˆ˜์ •๋œ ๊ฐ’, ์š”์ฒญ์œผ๋กœ ์˜จ ๊ฐ’๊ณผ ๋™์ผ + */ + + @Operation(summary = "03-04Briefing \uD83D\uDCF0 ๋ธŒ๋ฆฌํ•‘ ๋‚ด์šฉ ์ˆ˜์ •", description = "") + @Parameter(name = "id", description = "๋ธŒ๋ฆฌํ•‘ ์•„์ด๋””", example = "1") + @PatchMapping("/briefings/{id}") + public CommonResponse patchBriefingContent( + @PathVariable(name = "id") Long id, + @RequestBody @Valid BriefingRequestDTO.BriefingUpdateDTO request + ){ + + Briefing briefing = briefingCommandService.updateBriefing(id, request); + return CommonResponse.onSuccess(BriefingConverter.toBriefingUpdateDTO(briefing)); + } } diff --git a/src/main/java/briefing/briefing/api/BriefingConverter.java b/src/main/java/briefing/briefing/api/BriefingConverter.java index 81d7b79..0cffbe8 100644 --- a/src/main/java/briefing/briefing/api/BriefingConverter.java +++ b/src/main/java/briefing/briefing/api/BriefingConverter.java @@ -196,4 +196,13 @@ public static BriefingResponseDTO.BriefingV2PreviewListDTO toBriefingPreviewV2Te .briefings(tempDTOList) .build(); } + + public static BriefingResponseDTO.BriefingUpdateDTO toBriefingUpdateDTO(Briefing briefing){ + return BriefingResponseDTO.BriefingUpdateDTO + .builder() + .title(briefing.getTitle()) + .subTitle(briefing.getSubtitle()) + .content(briefing.getContent()) + .build(); + } } diff --git a/src/main/java/briefing/briefing/application/BriefingCommandService.java b/src/main/java/briefing/briefing/application/BriefingCommandService.java index bcfac21..b9ea463 100644 --- a/src/main/java/briefing/briefing/application/BriefingCommandService.java +++ b/src/main/java/briefing/briefing/application/BriefingCommandService.java @@ -1,7 +1,10 @@ package briefing.briefing.application; import java.util.List; +import java.util.Optional; +import briefing.exception.ErrorCode; +import briefing.exception.handler.BriefingException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +18,8 @@ import briefing.briefing.domain.repository.BriefingRepository; import lombok.RequiredArgsConstructor; +import javax.swing.text.html.Option; + @Service @Transactional @RequiredArgsConstructor @@ -36,4 +41,18 @@ public void createBriefing(final BriefingRequestDTO.BriefingCreate request) { articles.stream().map(article -> new BriefingArticle(briefing, article)).toList(); briefingArticleRepository.saveAll(briefingArticles); } + + public Briefing updateBriefing(Long id, final BriefingRequestDTO.BriefingUpdateDTO request){ + + // throw ๋ถ€๋ถ„์„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ validator annotation์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์„์ง€?? + Briefing briefing = briefingRepository.findById(id).orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); + + + // ์ด ์ฝ”๋“œ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋ฆฌํŒฉํ† ๋ง์ด ๊ฐ€๋Šฅํ• ์ง€?? + Optional.ofNullable(request.getContent()).ifPresent(briefing::setContent); + Optional.ofNullable(request.getTitle()).ifPresent(briefing::setTitle); + Optional.ofNullable(request.getSubTitle()).ifPresent(briefing::setSubtitle); + + return briefing; + } } diff --git a/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java index 794d524..3ca7078 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java @@ -7,7 +7,10 @@ import briefing.briefing.domain.BriefingType; import briefing.briefing.domain.TimeOfDay; import briefing.chatting.domain.GptModel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; public class BriefingRequestDTO { @@ -36,4 +39,11 @@ public static class BriefingCreate { TimeOfDay timeOfDay = TimeOfDay.MORNING; BriefingType briefingType = BriefingType.KOREA; } + + @Getter + public static class BriefingUpdateDTO{ + String title; + String subTitle; + String content; + } } diff --git a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java index 9f4b7bd..48c899d 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java @@ -125,4 +125,14 @@ public static class BriefingPreviewV2TempDTO { String subtitle; Integer scrapCount; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class BriefingUpdateDTO{ + String title; + String subTitle; + String content; + } } diff --git a/src/main/java/briefing/briefing/domain/Briefing.java b/src/main/java/briefing/briefing/domain/Briefing.java index e028401..9971fe7 100644 --- a/src/main/java/briefing/briefing/domain/Briefing.java +++ b/src/main/java/briefing/briefing/domain/Briefing.java @@ -53,4 +53,16 @@ public class Briefing extends BaseDateTimeEntity { public void setScrapCount(Integer scrapCount) { this.scrapCount = scrapCount; } + + public void setTitle(String title) { + this.title = title; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public void setContent(String content) { + this.content = content; + } } From 5ba651929799957f3185665e0bbbd0d9d5386b92 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Sun, 7 Jan 2024 14:19:01 +0900 Subject: [PATCH 25/28] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6945639..a209aaa 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,6 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation("org.springframework.boot:spring-boot-starter-oauth2-client") } def querydslSrcDir = 'src/main/generated' From ed388362da95beba876caedafc62f250579a5d78 Mon Sep 17 00:00:00 2001 From: CYY1007 Date: Sun, 7 Jan 2024 15:40:30 +0900 Subject: [PATCH 26/28] =?UTF-8?q?:hammer:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../briefing/application/BriefingCommandService.java | 7 +------ src/main/java/briefing/briefing/domain/Briefing.java | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/briefing/briefing/application/BriefingCommandService.java b/src/main/java/briefing/briefing/application/BriefingCommandService.java index b9ea463..171e159 100644 --- a/src/main/java/briefing/briefing/application/BriefingCommandService.java +++ b/src/main/java/briefing/briefing/application/BriefingCommandService.java @@ -44,14 +44,9 @@ public void createBriefing(final BriefingRequestDTO.BriefingCreate request) { public Briefing updateBriefing(Long id, final BriefingRequestDTO.BriefingUpdateDTO request){ - // throw ๋ถ€๋ถ„์„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ validator annotation์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์„์ง€?? Briefing briefing = briefingRepository.findById(id).orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING)); - - // ์ด ์ฝ”๋“œ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋ฆฌํŒฉํ† ๋ง์ด ๊ฐ€๋Šฅํ• ์ง€?? - Optional.ofNullable(request.getContent()).ifPresent(briefing::setContent); - Optional.ofNullable(request.getTitle()).ifPresent(briefing::setTitle); - Optional.ofNullable(request.getSubTitle()).ifPresent(briefing::setSubtitle); + briefing.updateBriefing(request.getTitle(),request.getSubTitle(),request.getContent()); return briefing; } diff --git a/src/main/java/briefing/briefing/domain/Briefing.java b/src/main/java/briefing/briefing/domain/Briefing.java index 9971fe7..bab2124 100644 --- a/src/main/java/briefing/briefing/domain/Briefing.java +++ b/src/main/java/briefing/briefing/domain/Briefing.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import jakarta.persistence.*; @@ -65,4 +66,10 @@ public void setSubtitle(String subtitle) { public void setContent(String content) { this.content = content; } + + public void updateBriefing(String title, String subtitle, String content){ + Optional.ofNullable(title).ifPresent(this::setTitle); + Optional.ofNullable(subtitle).ifPresent(this::setSubtitle); + Optional.ofNullable(content).ifPresent(this::setContent); + } } From 277e99a482f2adcf842327ff16b99d153a1a4a68 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sun, 7 Jan 2024 17:03:20 +0900 Subject: [PATCH 27/28] =?UTF-8?q?:bug:=20Fix:=20=EB=B8=8C=EB=A6=AC?= =?UTF-8?q?=ED=95=91=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EB=B2=84=EA=B7=B8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/BriefingCustomRepositoryImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java index b1b75d4..8eeefdb 100644 --- a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java +++ b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java @@ -1,5 +1,6 @@ package briefing.briefing.domain.repository; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -9,6 +10,9 @@ import org.springframework.stereotype.Repository; import com.querydsl.core.Tuple; +import com.querydsl.core.types.dsl.DateTemplate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import briefing.briefing.domain.Briefing; @@ -61,6 +65,11 @@ public List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type) { QBriefing briefing = QBriefing.briefing; QScrap scrap = QScrap.scrap; + DateTimePath dateTime = briefing.createdAt; + DateTemplate date = + Expressions.dateTemplate( + LocalDate.class, "DATE_FORMAT({0}, {1})", dateTime, "%Y-%m-%d"); + List results = queryFactory .select(briefing, scrap.count()) @@ -69,7 +78,7 @@ public List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type) { .on(scrap.briefing.eq(briefing)) .where(briefing.type.eq(type)) .groupBy(briefing) - .orderBy(briefing.createdAt.desc()) + .orderBy(date.desc(), briefing.ranks.desc()) .limit(10) .fetch(); From 66ae90e8a0475afd860ace9815d7c1e1d55082a5 Mon Sep 17 00:00:00 2001 From: swa07016 Date: Sun, 7 Jan 2024 17:23:03 +0900 Subject: [PATCH 28/28] =?UTF-8?q?:recycle:=20Refactor:=20=EC=95=A0?= =?UTF-8?q?=ED=94=8C=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=EB=8B=A8=20?= =?UTF-8?q?=EC=97=AD=EC=88=9C=20=EC=A0=95=EB=A0=AC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../briefing/application/strategy/BriefingV1QueryStrategy.java | 2 -- .../briefing/application/strategy/BriefingV2QueryStrategy.java | 2 -- .../domain/repository/BriefingCustomRepositoryImpl.java | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java index 2bc3ee3..43a5bd0 100644 --- a/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java +++ b/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -32,7 +31,6 @@ public List findBriefings(BriefingRequestParam.BriefingPreviewListPara if (briefingList.isEmpty()) { briefingList = briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(BriefingType.SOCIAL); - Collections.reverse(briefingList); } return briefingList; } diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java index ce35ca7..3cb28e2 100644 --- a/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java +++ b/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -34,7 +33,6 @@ public List findBriefings(BriefingRequestParam.BriefingPreviewListPara } briefingList = briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(params.getType()); - Collections.reverse(briefingList); return briefingList; } diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java index 8eeefdb..d34a3ba 100644 --- a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java +++ b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java @@ -78,7 +78,7 @@ public List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type) { .on(scrap.briefing.eq(briefing)) .where(briefing.type.eq(type)) .groupBy(briefing) - .orderBy(date.desc(), briefing.ranks.desc()) + .orderBy(date.desc(), briefing.ranks.asc()) .limit(10) .fetch();