From 3df50bac85ea3d8b436e228693d65bcc9fb7e0ba Mon Sep 17 00:00:00 2001 From: Byuk_mm Date: Sat, 31 Aug 2024 02:02:31 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[hotfix][#110]=20cors=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/server/bbo_gak/global/config/WebConfig.java | 7 +++++-- .../global/config/security/SecurityConfig.java | 10 ++++++---- src/main/resources/application-dev.yml | 11 +++++++++++ src/main/resources/application-local.yml | 11 +++++++++++ src/main/resources/application-prod.yml | 5 +++++ src/test/resources/application-test.yml | 13 ++++++++++++- 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/server/bbo_gak/global/config/WebConfig.java b/src/main/java/com/server/bbo_gak/global/config/WebConfig.java index dabb4c9..d42af8b 100644 --- a/src/main/java/com/server/bbo_gak/global/config/WebConfig.java +++ b/src/main/java/com/server/bbo_gak/global/config/WebConfig.java @@ -3,6 +3,7 @@ import com.server.bbo_gak.global.annotation.AuthenticationArgumentResolver; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @@ -20,12 +21,14 @@ public class WebConfig implements WebMvcConfigurer { private final AuthenticationArgumentResolver authenticationArgumentResolver; + @Value("${cors-allowed-origins}") + private List corsAllowedOrigins; + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://114.70.23.79:8080", "http://localhost:8080", "http://52.65.6.74:8080", - "http://localhost:3000", "https://www.bbogak.com", "https://dev.bbogak.com") + .allowedOrigins(corsAllowedOrigins.toArray(new String[0])) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); diff --git a/src/main/java/com/server/bbo_gak/global/config/security/SecurityConfig.java b/src/main/java/com/server/bbo_gak/global/config/security/SecurityConfig.java index 55d2bd6..280b8b7 100644 --- a/src/main/java/com/server/bbo_gak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/server/bbo_gak/global/config/security/SecurityConfig.java @@ -6,6 +6,7 @@ import com.server.bbo_gak.global.security.jwt.service.JwtTokenService; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @@ -27,10 +28,14 @@ public class SecurityConfig { private final JwtTokenService jwtTokenService; + private String[] allowUrls = {"/", "/api/v1/users/test/login", "/docs/**", "/v3/**", "/favicon.ico", "/api/v1/users/refreshToken", "/api/v1/users/social-login", "/api/v1/users/test/**", "/api/docs/**", "/api/v3/**", "/api/health-check/**"}; + @Value("${cors-allowed-origins}") + private List corsAllowedOrigins; + @Bean public WebSecurityCustomizer configure() { // filter 안타게 무시 @@ -63,10 +68,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins( - List.of("http://114.70.23.79:8080", "http://localhost:8080", "http://52.65.6.74:8080", - "http://localhost:3000", "http://118.67.129.12", "https://bbogak.com", "https://www.bbogak.com", - "https://dev.bbogak.com")); + configuration.setAllowedOrigins(corsAllowedOrigins); configuration.addAllowedMethod("*"); configuration.setAllowedHeaders(List.of("*")); // 허용할 헤더 configuration.setAllowCredentials(true); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index e33cbb5..aa3fd6c 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -10,3 +10,14 @@ logging: level: org.springframework.orm.jpa: INFO org.springframework.transaction: INFO + +cors-allowed-origins: + http://localhost:8080, + http://localhost:3000, + http://114.70.23.79:8080, + http://52.65.6.74:8080, + http://118.67.129.12, + https://bbogak.com, + https://www.bbogak.com, + https://dev.bbogak.com + https://web-dev.bbogak.com diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 7eb63c3..2e4f5ad 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -11,4 +11,15 @@ spring: hibernate: ddl-auto: update +cors-allowed-origins: + http://localhost:8080, + http://localhost:3000, + http://114.70.23.79:8080, + http://52.65.6.74:8080, + http://118.67.129.12, + https://bbogak.com, + https://www.bbogak.com, + https://dev.bbogak.com + https://web-dev.bbogak.com + diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 12bd0d4..5d81346 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -23,3 +23,8 @@ management: health: enabled: true +cors-allowed-origins: + https://bbogak.com, + https://www.bbogak.com, + https://dev.bbogak.com + https://web-dev.bbogak.com \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 443db87..0c1fa98 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -58,4 +58,15 @@ scheduler: sentry: dsn: https://b0e61ee9ddb2c12240759111df8b607c@o4505869811908608.ingest.us.sentry.io/4507831077240832 exception-resolver-order: -2147483647 - tracesSampleRate: 1.0 \ No newline at end of file + tracesSampleRate: 1.0 + +cors-allowed-origins: + http://localhost:8080, + http://localhost:3000, + http://114.70.23.79:8080, + http://52.65.6.74:8080, + http://118.67.129.12, + https://bbogak.com, + https://www.bbogak.com, + https://dev.bbogak.com + https://web-dev.bbogak.com \ No newline at end of file From c459167985c09d9ea892ece7c0eb04dbad3674c5 Mon Sep 17 00:00:00 2001 From: Byuk_mm Date: Sat, 31 Aug 2024 02:20:47 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[hotfix][#110]=20cors=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 2 +- src/main/resources/application-local.yml | 2 +- src/main/resources/application-prod.yml | 2 +- src/test/resources/application-test.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index aa3fd6c..42fd33a 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -19,5 +19,5 @@ cors-allowed-origins: http://118.67.129.12, https://bbogak.com, https://www.bbogak.com, - https://dev.bbogak.com + https://dev.bbogak.com, https://web-dev.bbogak.com diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 2e4f5ad..934b8d1 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -19,7 +19,7 @@ cors-allowed-origins: http://118.67.129.12, https://bbogak.com, https://www.bbogak.com, - https://dev.bbogak.com + https://dev.bbogak.com, https://web-dev.bbogak.com diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 5d81346..39260ab 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -26,5 +26,5 @@ management: cors-allowed-origins: https://bbogak.com, https://www.bbogak.com, - https://dev.bbogak.com + https://dev.bbogak.com, https://web-dev.bbogak.com \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 0c1fa98..c422373 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -68,5 +68,5 @@ cors-allowed-origins: http://118.67.129.12, https://bbogak.com, https://www.bbogak.com, - https://dev.bbogak.com + https://dev.bbogak.com, https://web-dev.bbogak.com \ No newline at end of file From 9f2f5035ee467036260355fbb9fbfc412f84c6e9 Mon Sep 17 00:00:00 2001 From: Byuk_mm Date: Sun, 8 Sep 2024 04:15:52 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[hotfix]=20Prod/Dev=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jenkins/Jenkinsfile-dev | 77 +++++++++++++++++++++++++ Jenkinsfile => jenkins/Jenkinsfile-prod | 6 +- src/main/resources/application-dev.yml | 16 ++++- src/main/resources/application-prod.yml | 10 ++-- 4 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 jenkins/Jenkinsfile-dev rename Jenkinsfile => jenkins/Jenkinsfile-prod (91%) diff --git a/jenkins/Jenkinsfile-dev b/jenkins/Jenkinsfile-dev new file mode 100644 index 0000000..7ae4287 --- /dev/null +++ b/jenkins/Jenkinsfile-dev @@ -0,0 +1,77 @@ +pipeline { + agent any + + tools { + jdk 'JDK21' + } + + environment { + DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials-id') + DOCKER_IMAGE = credentials('docker-image-dev') + K8S_URL = credentials('k8s-url') + K8S_NAMESPACE = credentials('k8s-namespace') + K8S_DEPLOY_NAME = credentials('k8s-deploy-name-dev') + JAVA_HOME = "${tool 'JDK21'}" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build & Test') { + steps { + script { + sh 'mkdir -p $WORKSPACE/src/main/resources/static/docs && touch $WORKSPACE/src/main/resources/static/docs/open-api-3.0.1.json' + sh './gradlew clean build -x test' + sh './gradlew openapi3' + } + } + post { + failure { + error 'Deployment failed!' + } + } + } + + stage('Login'){ + steps{ + sh 'echo $DOCKERHUB_CREDENTIALS_PSW | docker login -u $DOCKERHUB_CREDENTIALS_USR --password-stdin' // docker hub 로그인 + } + } + + stage('Build & Push Docker Image') { + steps { + script { + sh 'docker buildx build --push --platform linux/amd64 --build-arg PROFILE=dev -t $DOCKER_IMAGE .' + } + } + } + + stage('ssh-test') { + steps{ + script{ + sshagent (credentials: ['ncp-key']) { + sh """ + ssh -o StrictHostKeyChecking=no ${K8S_URL} << EOF + microk8s kubectl rollout restart deploy ${K8S_DEPLOY_NAME} -n=${K8S_NAMESPACE} + """ + } + } + } + } + + } + + post { + success { + echo 'Deployment was successful!' + } + failure { + error 'Deployment failed!' + } + } +} diff --git a/Jenkinsfile b/jenkins/Jenkinsfile-prod similarity index 91% rename from Jenkinsfile rename to jenkins/Jenkinsfile-prod index 4f033c2..14052ce 100644 --- a/Jenkinsfile +++ b/jenkins/Jenkinsfile-prod @@ -7,10 +7,10 @@ pipeline { environment { DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials-id') - DOCKER_IMAGE = credentials('docker-image') + DOCKER_IMAGE = credentials('docker-image-prod') K8S_URL = credentials('k8s-url') K8S_NAMESPACE = credentials('k8s-namespace') - K8S_DEPLOY_NAME = credentials('k8s-deploy-name') + K8S_DEPLOY_NAME = credentials('k8s-deploy-name-prod') JAVA_HOME = "${tool 'JDK21'}" PATH = "${env.JAVA_HOME}/bin:${env.PATH}" } @@ -26,7 +26,7 @@ pipeline { steps { script { sh 'mkdir -p $WORKSPACE/src/main/resources/static/docs && touch $WORKSPACE/src/main/resources/static/docs/open-api-3.0.1.json' - sh './gradlew clean build' + sh './gradlew clean build -x test' sh './gradlew openapi3' } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 42fd33a..2792971 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -8,8 +8,20 @@ spring: logging: level: - org.springframework.orm.jpa: INFO - org.springframework.transaction: INFO + root: info + org.springframework.security: info + +server: + forward-headers-strategy: framework + +management: + endpoints: + enabled-by-default: false + web: + base-path: /api/health-check + endpoint: + health: + enabled: true cors-allowed-origins: http://localhost:8080, diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 39260ab..c8253c9 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -3,11 +3,6 @@ spring: activate: on-profile: prod jpa: - properties: - hibernate: - show_sql: true - format_sql: true - use_sql_comments: true hibernate: ddl-auto: update @@ -23,6 +18,11 @@ management: health: enabled: true +logging: + level: + root: error + org.springframework.security: error + cors-allowed-origins: https://bbogak.com, https://www.bbogak.com, From 27de5e22b9e95001fa0b02eecd1e5e1de72e9173 Mon Sep 17 00:00:00 2001 From: Byuk_mm Date: Wed, 11 Sep 2024 00:56:48 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[feature][#117]=20tag=20=EA=B2=80=EC=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../card/controller/CardSearchController.java | 29 ++++++++ .../bbo_gak/domain/card/dao/CardDao.java | 17 +---- .../request/CardSearchByTagListRequest.java | 24 +++++++ .../response/CardSearchByTagListResponse.java | 44 ++++++++++++ .../card/service/CardSearchService.java | 49 ++++++++++++++ .../domain/card/service/CardService.java | 10 +-- .../domain/card/service/TagService.java | 14 ---- .../controller/CardSearchControllerTest.java | 67 +++++++++++++++++++ 8 files changed, 218 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java create mode 100644 src/main/java/com/server/bbo_gak/domain/card/dto/request/CardSearchByTagListRequest.java create mode 100644 src/main/java/com/server/bbo_gak/domain/card/dto/response/CardSearchByTagListResponse.java create mode 100644 src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java create mode 100644 src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java diff --git a/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java b/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java new file mode 100644 index 0000000..c8fa6e2 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java @@ -0,0 +1,29 @@ +package com.server.bbo_gak.domain.card.controller; + +import com.server.bbo_gak.domain.card.dto.response.CardSearchByTagListResponse; +import com.server.bbo_gak.domain.card.service.CardSearchService; +import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.annotation.AuthUser; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1") +@RequiredArgsConstructor +public class CardSearchController { + + private final CardSearchService cardSearchService; + + @GetMapping("/search/cards") + public ResponseEntity> searchCardByTagList( + @AuthUser User user, + @RequestParam(value = "card-type-value-group", required = false) String cardTypeValueGroup, + @RequestParam(value = "tag-ids") List tagIdList) { + return ResponseEntity.ok(cardSearchService.searchCardByTagList(user, cardTypeValueGroup, tagIdList)); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/card/dao/CardDao.java b/src/main/java/com/server/bbo_gak/domain/card/dao/CardDao.java index f28b087..8a32924 100644 --- a/src/main/java/com/server/bbo_gak/domain/card/dao/CardDao.java +++ b/src/main/java/com/server/bbo_gak/domain/card/dao/CardDao.java @@ -1,6 +1,5 @@ package com.server.bbo_gak.domain.card.dao; -import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; import com.server.bbo_gak.domain.card.entity.Card; import com.server.bbo_gak.domain.card.entity.CardTypeValue; @@ -28,8 +27,9 @@ public List findAllByUserIdAndCardTypeValueList(User user, CardTypeValue[] return query.selectFrom(qCard) .leftJoin(qCard.cardTypeList, qCardType).fetchJoin() .where(qCard.user.id.eq(user.getId()) + .and(cardTypeValueList.length == 0 ? null : qCardType.cardTypeValue.in(cardTypeValueList)) .and(qCardType.cardTypeValue.in(cardTypeValueList)) - .and(createRecruitBooleanBuilder(qCard, recruitId))) + .and(recruitId == null ? null : qCard.recruit.id.eq(recruitId))) .distinct() .fetch(); } @@ -43,20 +43,9 @@ public List findAllByUserIdAndCardTypeValue(User user, CardTypeValue cardT .leftJoin(qCard.cardTypeList, qCardType).fetchJoin() .where(qCard.user.id.eq(user.getId()) .and(qCardType.cardTypeValue.eq(cardTypeValue)) - .and(createRecruitBooleanBuilder(qCard, recruitId)) + .and(recruitId == null ? null : qCard.recruit.id.eq(recruitId)) ) .distinct() .fetch(); } - - private BooleanBuilder createRecruitBooleanBuilder(QCard qCard, Long recruitId) { - - BooleanBuilder builder = new BooleanBuilder(); - - if (recruitId == null) { - return null; - } - - return builder.and(qCard.recruit.id.eq(recruitId)); - } } diff --git a/src/main/java/com/server/bbo_gak/domain/card/dto/request/CardSearchByTagListRequest.java b/src/main/java/com/server/bbo_gak/domain/card/dto/request/CardSearchByTagListRequest.java new file mode 100644 index 0000000..3cb6bbf --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/card/dto/request/CardSearchByTagListRequest.java @@ -0,0 +1,24 @@ +package com.server.bbo_gak.domain.card.dto.request; + +import com.server.bbo_gak.domain.card.entity.CardTypeValue; +import com.server.bbo_gak.domain.card.entity.CardTypeValueGroup; +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.Optional; + +public record CardSearchByTagListRequest( + + @Size(min = 1) + List tagIdList, + + @Nullable + String cardTypeValueGroup +) { + + public CardTypeValue[] getCardTypeValueList() { + return Optional.ofNullable(cardTypeValueGroup) + .map(cardTypeValueGroup -> CardTypeValueGroup.findByValue(cardTypeValueGroup).getCardTypeValueList()) + .orElseGet(() -> new CardTypeValue[0]); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/card/dto/response/CardSearchByTagListResponse.java b/src/main/java/com/server/bbo_gak/domain/card/dto/response/CardSearchByTagListResponse.java new file mode 100644 index 0000000..6fa1de7 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/card/dto/response/CardSearchByTagListResponse.java @@ -0,0 +1,44 @@ +package com.server.bbo_gak.domain.card.dto.response; + +import com.server.bbo_gak.domain.card.entity.Card; +import com.server.bbo_gak.domain.card.entity.CardTypeValue; +import com.server.bbo_gak.domain.card.entity.CardTypeValueGroup; +import com.server.bbo_gak.domain.recruit.entity.Recruit; +import com.server.bbo_gak.global.utils.BaseDateTimeFormatter; +import java.util.List; +import java.util.Optional; +import lombok.AccessLevel; +import lombok.Builder; + +@Builder(access = AccessLevel.PRIVATE) +public record CardSearchByTagListResponse( + Long id, + String title, + String updatedDate, + List tagList, + String cardTypeValueGroup, + String cardTypeValue, + String recruitTitle, + String content +) { + + public static CardSearchByTagListResponse from(Card card) { + + CardTypeValue cardTypeValue = card.getCardTypeList().getFirst().getCardTypeValue(); + + return CardSearchByTagListResponse.builder() + .id(card.getId()) + .title(card.getTitle()) + .updatedDate(card.getUpdatedDate().format(BaseDateTimeFormatter.getLocalDateTimeFormatter())) + .tagList( + card.getCardTagList().stream() + .map(cardTag -> TagGetResponse.from(cardTag.getTag())) + .toList() + ) + .cardTypeValueGroup(CardTypeValueGroup.findByCardTypeValue(cardTypeValue).getValue()) + .recruitTitle(Optional.ofNullable(card.getRecruit()).map(Recruit::getTitle).orElse(null)) + .cardTypeValue(cardTypeValue.getValue()) + .content(card.getContent()) + .build(); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java b/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java new file mode 100644 index 0000000..99a453a --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java @@ -0,0 +1,49 @@ +package com.server.bbo_gak.domain.card.service; + +import com.server.bbo_gak.domain.card.dao.CardDao; +import com.server.bbo_gak.domain.card.dao.TagRepository; +import com.server.bbo_gak.domain.card.dto.response.CardSearchByTagListResponse; +import com.server.bbo_gak.domain.card.entity.Card; +import com.server.bbo_gak.domain.card.entity.CardTypeValue; +import com.server.bbo_gak.domain.card.entity.CardTypeValueGroup; +import com.server.bbo_gak.domain.card.entity.Tag; +import com.server.bbo_gak.domain.user.entity.User; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CardSearchService { + + private final TagRepository tagRepository; + private final CardDao cardDao; + + @Transactional(readOnly = true) + public List searchCardByTagList(User user, String cardTypeValueGroup, + List tagIdList) { + + List tagList = tagRepository.findAllById(tagIdList); + + List cards = cardDao.findAllByUserIdAndCardTypeValueList(user, getCardTypeValueList(cardTypeValueGroup), + null); + + // TODO 필터링 로직 디비로 가게 하기 + return cards.stream() + .filter(card -> tagList.isEmpty() || card.isTagListContain(tagList)) + .sorted(Comparator.comparing(Card::getUpdatedDate).reversed()) + .map(CardSearchByTagListResponse::from) + .collect(Collectors.toList()); + } + + private CardTypeValue[] getCardTypeValueList(String cardTypeValueGroupInput) { + return Optional.ofNullable(cardTypeValueGroupInput) + .map(cardTypeValueGroup -> CardTypeValueGroup.findByValue(cardTypeValueGroup).getCardTypeValueList()) + .orElseGet(() -> new CardTypeValue[0]); + } + +} diff --git a/src/main/java/com/server/bbo_gak/domain/card/service/CardService.java b/src/main/java/com/server/bbo_gak/domain/card/service/CardService.java index f374d5f..f05c27f 100644 --- a/src/main/java/com/server/bbo_gak/domain/card/service/CardService.java +++ b/src/main/java/com/server/bbo_gak/domain/card/service/CardService.java @@ -76,15 +76,9 @@ public List getCardList(User user, String cardTypeValue, Li List cards = cardDao.findAllByUserIdAndCardTypeValue(user, CardTypeValue.findByValue(cardTypeValue), null); + // TODO 필터링 로직 디비로 가게 하기 return cards.stream() - .filter(card -> { - - if (tagList.isEmpty()) { - return true; - } - - return card.isTagListContain(tagList); - }) + .filter(card -> tagList.isEmpty() || card.isTagListContain(tagList)) .sorted(Comparator.comparing(Card::getUpdatedDate).reversed()) .map(card -> CardListGetResponse.of(card, card.getCardTagList())) .collect(Collectors.toList()); diff --git a/src/main/java/com/server/bbo_gak/domain/card/service/TagService.java b/src/main/java/com/server/bbo_gak/domain/card/service/TagService.java index a3b5d28..2195907 100644 --- a/src/main/java/com/server/bbo_gak/domain/card/service/TagService.java +++ b/src/main/java/com/server/bbo_gak/domain/card/service/TagService.java @@ -8,7 +8,6 @@ import com.server.bbo_gak.domain.card.entity.Card; import com.server.bbo_gak.domain.card.entity.CardTag; import com.server.bbo_gak.domain.card.entity.CardTypeValue; -import com.server.bbo_gak.domain.card.entity.CardTypeValueGroup; import com.server.bbo_gak.domain.card.entity.Tag; import com.server.bbo_gak.domain.recruit.dao.RecruitRepository; import com.server.bbo_gak.domain.recruit.entity.Recruit; @@ -16,7 +15,6 @@ import com.server.bbo_gak.domain.user.entity.User; import com.server.bbo_gak.global.error.exception.BusinessException; import com.server.bbo_gak.global.error.exception.ErrorCode; -import com.server.bbo_gak.global.error.exception.InvalidValueException; import com.server.bbo_gak.global.error.exception.NotFoundException; import java.util.List; import lombok.RequiredArgsConstructor; @@ -109,16 +107,4 @@ private void validateTagDuplicated(Long tagId, Card card) { } } } - - private CardTypeValueGroup getRecruitCardTypeValueGroup(String type) { - CardTypeValueGroup cardTypeValueGroup = CardTypeValueGroup.findByCardTypeValue(CardTypeValue.findByValue(type)); - - if (cardTypeValueGroup.equals(CardTypeValueGroup.RECRUIT)) { - throw new InvalidValueException(ErrorCode.RECRUIT_CARD_TYPE_NOT_MATCHED); - } - - return cardTypeValueGroup; - } - - } diff --git a/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java b/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java new file mode 100644 index 0000000..cfb9460 --- /dev/null +++ b/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java @@ -0,0 +1,67 @@ +package com.server.bbo_gak.domain.card.controller; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.epages.restdocs.apispec.Schema; +import com.server.bbo_gak.global.AbstractRestDocsTests; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@SpringBootTest +@ActiveProfiles("test") +@Sql({"/all-data-delete.sql", "/card-test-data.sql"}) +public class CardSearchControllerTest extends AbstractRestDocsTests { + + private static final String DEFAULT_URL = "/api/v1"; + + private final String cardSearch = "CardSearch"; + + @Nested + class 카드_검색_태그_리스트로 { + + @Test + public void 성공() throws Exception { + + mockMvc.perform(get(DEFAULT_URL + "/search/cards").contentType(MediaType.APPLICATION_JSON) + .queryParam("tag-ids", "1,2") + .queryParam("card-type-value-group", "내_정보") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("[카드_검색_태그_리스트로] 성공", preprocessResponse(prettyPrint()), resource(getBuild()))); + } + + private ResourceSnippetParameters getBuild() { + return ResourceSnippetParameters.builder().description("카드_검색_태그_리스트로").tags(cardSearch) + .queryParameters( + parameterWithName("tag-ids").description("태그 아이디 리스트"), + parameterWithName("card-type-value-group").description("카드 타입 그룹").optional()) + .responseSchema(Schema.schema("CardListGetResponse")) + .responseFields(fieldWithPath("[].id").type(JsonFieldType.NUMBER).description("Card ID"), + fieldWithPath("[].title").type(JsonFieldType.STRING).description("Card 제목"), + fieldWithPath("[].updatedDate").type(JsonFieldType.STRING).description("Card 수정일시"), + fieldWithPath("[].tagList.[].id").type(JsonFieldType.NUMBER).description("태그 ID"), + fieldWithPath("[].tagList.[].name").type(JsonFieldType.STRING).description("태그 이름"), + fieldWithPath("[].tagList.[].type").type(JsonFieldType.STRING).description("태그 타입"), + fieldWithPath("[].cardTypeValueGroup").type(JsonFieldType.STRING).description("Card 타입 그룹"), + fieldWithPath("[].cardTypeValue").type(JsonFieldType.STRING).description("Card 타입"), + fieldWithPath("[].recruitTitle").type(JsonFieldType.STRING).description("Card 공고 제목").optional(), + fieldWithPath("[].content").type(JsonFieldType.STRING).description("Card 본문")) + .build(); + } + } +} From 68e27d616f003549717be2f64254f8d4f3be5c0f Mon Sep 17 00:00:00 2001 From: Byuk_mm Date: Wed, 11 Sep 2024 01:47:30 +0900 Subject: [PATCH 5/5] [feature][#117] card tag search history --- .../card/controller/CardSearchController.java | 6 +++ .../dao/CardTagSearchHistoryRepository.java | 11 +++++ .../card/entity/CardTagSearchHistory.java | 43 +++++++++++++++++++ .../card/service/CardSearchService.java | 17 +++++++- .../controller/CardSearchControllerTest.java | 25 +++++++++++ src/test/resources/all-data-delete.sql | 1 + src/test/resources/card-test-data.sql | 9 +++- 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/server/bbo_gak/domain/card/dao/CardTagSearchHistoryRepository.java create mode 100644 src/main/java/com/server/bbo_gak/domain/card/entity/CardTagSearchHistory.java diff --git a/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java b/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java index c8fa6e2..d0b317c 100644 --- a/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java +++ b/src/main/java/com/server/bbo_gak/domain/card/controller/CardSearchController.java @@ -1,6 +1,7 @@ package com.server.bbo_gak.domain.card.controller; import com.server.bbo_gak.domain.card.dto.response.CardSearchByTagListResponse; +import com.server.bbo_gak.domain.card.dto.response.TagGetResponse; import com.server.bbo_gak.domain.card.service.CardSearchService; import com.server.bbo_gak.domain.user.entity.User; import com.server.bbo_gak.global.annotation.AuthUser; @@ -26,4 +27,9 @@ public ResponseEntity> searchCardByTagList( @RequestParam(value = "tag-ids") List tagIdList) { return ResponseEntity.ok(cardSearchService.searchCardByTagList(user, cardTypeValueGroup, tagIdList)); } + + @GetMapping("/search/card-tag-history") + public ResponseEntity> getCardTagSearchHistory(@AuthUser User user) { + return ResponseEntity.ok(cardSearchService.getCardTagSearchHistoryList(user)); + } } diff --git a/src/main/java/com/server/bbo_gak/domain/card/dao/CardTagSearchHistoryRepository.java b/src/main/java/com/server/bbo_gak/domain/card/dao/CardTagSearchHistoryRepository.java new file mode 100644 index 0000000..b22f077 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/card/dao/CardTagSearchHistoryRepository.java @@ -0,0 +1,11 @@ +package com.server.bbo_gak.domain.card.dao; + +import com.server.bbo_gak.domain.card.entity.CardTagSearchHistory; +import com.server.bbo_gak.domain.user.entity.User; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CardTagSearchHistoryRepository extends JpaRepository { + + List findTop10ByUserOrderByCreatedDate(User user); +} diff --git a/src/main/java/com/server/bbo_gak/domain/card/entity/CardTagSearchHistory.java b/src/main/java/com/server/bbo_gak/domain/card/entity/CardTagSearchHistory.java new file mode 100644 index 0000000..0f5b3a4 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/card/entity/CardTagSearchHistory.java @@ -0,0 +1,43 @@ +package com.server.bbo_gak.domain.card.entity; + +import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; + +@Getter +@Entity +@SQLRestriction("deleted = false") +@SQLDelete(sql = "UPDATE card_tag_search_history SET deleted = true WHERE card_tag_search_history_id = ?") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CardTagSearchHistory extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "card_tag_search_history_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tag_id") + private Tag tag; + + public CardTagSearchHistory(User user, Tag tag) { + this.user = user; + this.tag = tag; + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java b/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java index 99a453a..124cec5 100644 --- a/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java +++ b/src/main/java/com/server/bbo_gak/domain/card/service/CardSearchService.java @@ -1,9 +1,12 @@ package com.server.bbo_gak.domain.card.service; import com.server.bbo_gak.domain.card.dao.CardDao; +import com.server.bbo_gak.domain.card.dao.CardTagSearchHistoryRepository; import com.server.bbo_gak.domain.card.dao.TagRepository; import com.server.bbo_gak.domain.card.dto.response.CardSearchByTagListResponse; +import com.server.bbo_gak.domain.card.dto.response.TagGetResponse; import com.server.bbo_gak.domain.card.entity.Card; +import com.server.bbo_gak.domain.card.entity.CardTagSearchHistory; import com.server.bbo_gak.domain.card.entity.CardTypeValue; import com.server.bbo_gak.domain.card.entity.CardTypeValueGroup; import com.server.bbo_gak.domain.card.entity.Tag; @@ -20,10 +23,11 @@ @RequiredArgsConstructor public class CardSearchService { + private final CardTagSearchHistoryRepository cardTagSearchHistoryRepository; private final TagRepository tagRepository; private final CardDao cardDao; - @Transactional(readOnly = true) + @Transactional public List searchCardByTagList(User user, String cardTypeValueGroup, List tagIdList) { @@ -32,6 +36,10 @@ public List searchCardByTagList(User user, String c List cards = cardDao.findAllByUserIdAndCardTypeValueList(user, getCardTypeValueList(cardTypeValueGroup), null); + cardTagSearchHistoryRepository.saveAll(tagList.stream() + .map(tag -> new CardTagSearchHistory(user, tag)) + .toList()); + // TODO 필터링 로직 디비로 가게 하기 return cards.stream() .filter(card -> tagList.isEmpty() || card.isTagListContain(tagList)) @@ -40,6 +48,13 @@ public List searchCardByTagList(User user, String c .collect(Collectors.toList()); } + @Transactional(readOnly = true) + public List getCardTagSearchHistoryList(User user) { + return cardTagSearchHistoryRepository.findTop10ByUserOrderByCreatedDate(user).stream() + .map(cardTagSearchHistory -> TagGetResponse.from(cardTagSearchHistory.getTag())) + .toList(); + } + private CardTypeValue[] getCardTypeValueList(String cardTypeValueGroupInput) { return Optional.ofNullable(cardTypeValueGroupInput) .map(cardTypeValueGroup -> CardTypeValueGroup.findByValue(cardTypeValueGroup).getCardTypeValueList()) diff --git a/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java b/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java index cfb9460..9ee1372 100644 --- a/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java +++ b/src/test/java/com/server/bbo_gak/domain/card/controller/CardSearchControllerTest.java @@ -64,4 +64,29 @@ private ResourceSnippetParameters getBuild() { .build(); } } + + @Nested + class 카드_태그_검색_히스토리_조회 { + + @Test + @Transactional + public void 성공() throws Exception { + + mockMvc.perform(get(DEFAULT_URL + "/search/card-tag-history").contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("[카드_태그_검색_히스토리_조회] 성공", preprocessResponse(prettyPrint()), resource(getBuild()))); + } + + private ResourceSnippetParameters getBuild() { + return ResourceSnippetParameters.builder() + .description("카드_태그_검색_히스토리_조회").tags(cardSearch) + .responseSchema(Schema.schema("TagGetResponse")) + .responseFields( + fieldWithPath("[].id").type(JsonFieldType.NUMBER).description("태그 ID"), + fieldWithPath("[].name").type(JsonFieldType.STRING).description("태그 이름"), + fieldWithPath("[].type").type(JsonFieldType.STRING).description("태그 타입")) + .build(); + } + } } diff --git a/src/test/resources/all-data-delete.sql b/src/test/resources/all-data-delete.sql index 39a670e..c87007b 100644 --- a/src/test/resources/all-data-delete.sql +++ b/src/test/resources/all-data-delete.sql @@ -1,5 +1,6 @@ SET REFERENTIAL_INTEGRITY FALSE; truncate table card_memo; +truncate table card_tag_search_history; truncate table card_image; truncate table notification; truncate table recruit_schedule; diff --git a/src/test/resources/card-test-data.sql b/src/test/resources/card-test-data.sql index a76ced9..75cb6a9 100644 --- a/src/test/resources/card-test-data.sql +++ b/src/test/resources/card-test-data.sql @@ -103,6 +103,13 @@ VALUES (false, 2, 5, '2024-07-24 21:26:31.000000', 'test contents 222', '2024-07 INSERT INTO card_memo (deleted, card_id, card_memo_id, created_at, content, update_at) VALUES (false, 3, 6, '2024-07-24 21:26:31.000000', 'test contents 333', '2024-07-24 21:26:42.000000'); - +INSERT INTO card_tag_search_history (deleted, user_id, card_tag_search_history_id, created_at, tag_id, update_at) +VALUES (false, 1, 1, '2024-07-24 21:26:28.000000', 1, '2024-07-24 21:26:40.000000'); +INSERT INTO card_tag_search_history (deleted, user_id, card_tag_search_history_id, created_at, tag_id, update_at) +VALUES (false, 1, 2, '2024-07-24 21:26:29.000000', 2, '2024-07-24 21:26:41.000000'); +INSERT INTO card_tag_search_history (deleted, user_id, card_tag_search_history_id, created_at, tag_id, update_at) +VALUES (false, 1, 3, '2024-07-24 21:26:28.000000', 1, '2024-07-24 21:26:40.000000'); +INSERT INTO card_tag_search_history (deleted, user_id, card_tag_search_history_id, created_at, tag_id, update_at) +VALUES (false, 1, 4, '2024-07-24 21:26:29.000000', 2, '2024-07-24 21:26:41.000000');