From 39035578f170cd5ba1a857be9290492c662f13ab Mon Sep 17 00:00:00 2001 From: SYDLK Date: Wed, 21 Jan 2026 11:04:46 +0900 Subject: [PATCH 01/11] =?UTF-8?q?build[querydsl]:=20QueryDSL=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index c89ab8d..56ab593 100644 --- a/build.gradle +++ b/build.gradle @@ -40,11 +40,12 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' - // 5. QueryDSL (스프링 부트 3.x 이상은 Jakarta 버전 필수) - // implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' - // annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta" - // annotationProcessor "jakarta.annotation:jakarta.annotation-api" - // annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // 5. QueryDSL (스프링 부트 3.x 이상은 Jakarta 버전 필수) - OpenFeign + implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0" + implementation "io.github.openfeign.querydsl:querydsl-core:7.0" + annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" // 6. Swagger (SpringDoc OpenAPI - 스프링 부트 3.4와 호완을 위해 2.7 이상을 사용) implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3' @@ -59,19 +60,21 @@ tasks.withType(Test).configureEach { useJUnitPlatform() } -// QueryDSL Q클래스 생성 위치를 깔끔하게 정리 (선택 사항이지만 추천) -/* -def querydslDir = "$buildDir/generated/querydsl" - -tasks.withType(JavaCompile).configureEach { - options.getGeneratedSourceOutputDirectory().set(file(querydslDir)) -} +// QueryDSL 관련 설정 +// generated/querydsl 폴더 생성 & 삽입 +def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile +// 소스 세트에 생성 경로 추가 (구체적인 경로 지정) sourceSets { main.java.srcDirs += [ querydslDir ] } -clean { - delete file(querydslDir) +// 컴파일 시 생성 경로 지정 +tasks.withType(JavaCompile).configureEach { + options.generatedSourceOutputDirectory.set(querydslDir) } - */ \ No newline at end of file + +// clean 태스크에 생성 폴더 삭제 로직 추가 +clean.doLast { + file(querydslDir).deleteDir() +} \ No newline at end of file From 6680e07ab6223972bc51eb0e475d03cbcbcbb683 Mon Sep 17 00:00:00 2001 From: SYDLK Date: Thu, 22 Jan 2026 02:33:35 +0900 Subject: [PATCH 02/11] =?UTF-8?q?build[lombok]:=20commons-lang3=20?= =?UTF-8?q?=EB=B3=B4=EC=95=88=EC=B7=A8=EC=95=BD=EC=A0=90=20=EC=9A=B0?= =?UTF-8?q?=ED=9A=8C,=20=EB=A1=AC=EB=B3=B5=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [롬복 (Lombok) Java 21 + query dsl 조합시 롬복 오류로 인해 수정하였습니다.] --- build.gradle | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 56ab593..e814551 100644 --- a/build.gradle +++ b/build.gradle @@ -30,9 +30,13 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' // 실제 서버용 (MySQL) runtimeOnly 'com.h2database:h2' // 테스트, 로컬용 (H2) - // 3. 롬복 (Lombok) - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' + // 3. 롬복 (Lombok) Java 21 + query dsl 조합시 롬복 오류로 인해 수정하였습니다. +// compileOnly 'org.projectlombok:lombok' +// annotationProcessor 'org.projectlombok:lombok' + + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' + // 4. 인증/인가 (Security + JWT) implementation 'org.springframework.boot:spring-boot-starter-security' @@ -41,9 +45,9 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' // 5. QueryDSL (스프링 부트 3.x 이상은 Jakarta 버전 필수) - OpenFeign - implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0" - implementation "io.github.openfeign.querydsl:querydsl-core:7.0" - annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa" + implementation "io.github.openfeign.querydsl:querydsl-jpa:7.1" + implementation "io.github.openfeign.querydsl:querydsl-core:7.1" + annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.1:jpa" annotationProcessor "jakarta.persistence:jakarta.persistence-api" annotationProcessor "jakarta.annotation:jakarta.annotation-api" @@ -54,6 +58,11 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' // 시큐리티 테스트용 testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testCompileOnly 'org.projectlombok:lombok:1.18.32' // 텍스트 롬복 추가 + testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' + + // 8. 보안 취약점 우회 - commons-lang3 transitive dependency override + implementation 'org.apache.commons:commons-lang3:3.17.1' } tasks.withType(Test).configureEach { From 4f983e0698f1f0c182ab8b9847f62c5f813b10bf Mon Sep 17 00:00:00 2001 From: SYDLK Date: Thu, 22 Jan 2026 03:31:07 +0900 Subject: [PATCH 03/11] =?UTF-8?q?build[wip]:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/apiwiki/domain/api/dto/ApiDTO.java | 22 ++++ .../domain/api/repository/ApiQueryDsl.java | 10 ++ .../api/repository/ApiQueryDslImpl.java | 35 ++++++ .../domain/api/repository/ApiRepository.java | 6 + .../domain/api/service/ApiQueryService.java | 115 ++++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 src/main/java/com/umc/apiwiki/domain/api/dto/ApiDTO.java create mode 100644 src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java create mode 100644 src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java create mode 100644 src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java create mode 100644 src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java diff --git a/src/main/java/com/umc/apiwiki/domain/api/dto/ApiDTO.java b/src/main/java/com/umc/apiwiki/domain/api/dto/ApiDTO.java new file mode 100644 index 0000000..347f81e --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/dto/ApiDTO.java @@ -0,0 +1,22 @@ +package com.umc.apiwiki.domain.api.dto; + +import com.umc.apiwiki.domain.api.enums.AuthType; +import com.umc.apiwiki.domain.api.enums.PricingType; +import com.umc.apiwiki.domain.api.enums.ProviderCompany; + +import java.math.BigDecimal; + +public class ApiDTO { + // API 목록 미리보기 DTO(Explore / 필터용) + public record ApiPreview( + Long apiId, + String name, + String summary, + BigDecimal avgRating, + Long reviewCount, + Long viewCounts, + PricingType pricingType, + AuthType authType, + ProviderCompany providerCompany + ) { } +} diff --git a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java new file mode 100644 index 0000000..a02e489 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java @@ -0,0 +1,10 @@ +package com.umc.apiwiki.domain.api.repository; + +import com.querydsl.core.types.Predicate; +import com.umc.apiwiki.domain.api.entity.Api; + +import java.util.List; + +public interface ApiQueryDsl { + List searchApi(Predicate predicate); +} diff --git a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java new file mode 100644 index 0000000..1a3518b --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java @@ -0,0 +1,35 @@ +package com.umc.apiwiki.domain.api.repository; + +import com.querydsl.core.types.Predicate; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.umc.apiwiki.domain.api.entity.Api; +import com.umc.apiwiki.domain.api.entity.QApi; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +@RequiredArgsConstructor +public class ApiQueryDslImpl implements ApiQueryDsl { + private final EntityManager em; + + // 검색 API + @Override + public List searchApi( + Predicate predicate + ){ + // JPA 세팅 + JPAQueryFactory queryFactory = new JPAQueryFactory(em); + + // Q 클래스 선언 + QApi api = QApi.api; + + return queryFactory + .selectFrom(api) + .where(predicate) + .fetch(); + } +} diff --git a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java new file mode 100644 index 0000000..397a2e6 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java @@ -0,0 +1,6 @@ +package com.umc.apiwiki.domain.api.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ApiRepository extends JpaRepository , ApiQueryDsl { +} diff --git a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java new file mode 100644 index 0000000..7a8b73d --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java @@ -0,0 +1,115 @@ +package com.umc.apiwiki.domain.api.service; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.umc.apiwiki.domain.api.dto.ApiDTO; +import com.umc.apiwiki.domain.api.entity.QApi; +import com.umc.apiwiki.domain.api.enums.AuthType; +import com.umc.apiwiki.domain.api.enums.PricingType; +import com.umc.apiwiki.domain.api.enums.ProviderCompany; +import com.umc.apiwiki.domain.api.repository.ApiRepository; +import com.umc.apiwiki.domain.community.entity.review.QApiReview; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ApiQueryService { + + private final ApiRepository apiRepository; + private final EntityManager entityManager; + + // Explore / 필터용 API 목록 조회 + public Page searchApis( + int page, + Integer size, + ProviderCompany providerCompany, + AuthType authType, + PricingType pricingType, + BigDecimal minRating + ) { + + QApi api = QApi.api; + QApiReview review = QApiReview.apiReview; + + JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + + BooleanBuilder builder = new BooleanBuilder(); + + // 동적 쿼리: 검색 조건 + if (providerCompany != null) { + builder.and(api.providerCompany.eq(providerCompany)); + } + if (authType != null) { + builder.and(api.authType.eq(authType)); + } + if (pricingType != null) { + builder.and(api.pricingType.eq(pricingType)); + } + if (minRating != null) { + builder.and(api.avgRating.goe(minRating)); + } + + int pageSize = (size != null) ? size : 16; + Pageable pageable = PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); + + // reviewCount 서브쿼리 + var reviewCountSubQuery = + JPAExpressions.select(review.count()) + .from(review) + .where(review.api.id.eq(api.id)); + + // 목록 조회 + DTO 직접 생성 + List content = queryFactory + .select(Projections.constructor( + ApiDTO.ApiPreview.class, + api.id, + api.name, + api.summary, + api.avgRating, + reviewCountSubQuery, // Long + api.viewCounts, // Long (null 가능) + api.pricingType, + api.authType, + api.providerCompany + )) + .from(api) + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + // total count + Long total = queryFactory + .select(api.count()) + .from(api) + .where(builder) + .fetchOne(); + + long totalCount = (total != null) ? total : 0L; + + // null 안전처리 + content = content.stream() + .map(p -> new ApiDTO.ApiPreview( + p.apiId(), + p.name(), + p.summary(), + p.avgRating() != null ? p.avgRating() : BigDecimal.ZERO, + p.reviewCount() != null ? p.reviewCount() : 0L, + p.viewCounts() != null ? p.viewCounts() : 0L, + p.pricingType(), + p.authType(), + p.providerCompany() + )) + .toList(); + + return new PageImpl<>(content, pageable, totalCount); + } +} \ No newline at end of file From 446b78b21a727dc6788ec3371bf6e5cb37043b9e Mon Sep 17 00:00:00 2001 From: SYDLK Date: Thu, 22 Jan 2026 19:53:57 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat[wip]:=20ApiController.java=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/api/controller/ApiController.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java diff --git a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java new file mode 100644 index 0000000..97777e6 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java @@ -0,0 +1,45 @@ +package com.umc.apiwiki.domain.api.controller; + +import com.umc.apiwiki.domain.api.dto.ApiDTO; +import com.umc.apiwiki.domain.api.enums.AuthType; +import com.umc.apiwiki.domain.api.enums.PricingType; +import com.umc.apiwiki.domain.api.enums.ProviderCompany; +import com.umc.apiwiki.domain.api.service.ApiQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +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; + + +import java.math.BigDecimal; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/apis") +public class ApiController { + + private final ApiQueryService apiQueryService; + + @GetMapping + public ApiResponse> searchApis( + @RequestParam int page, + @RequestParam(required = false) Integer size, + @RequestParam(required = false, name = "providers") ProviderCompany providerCompany, + @RequestParam(required = false, name = "authTypes") AuthType authType, + @RequestParam(required = false, name = "pricingTypes") PricingType pricingType, + @RequestParam(required = false) BigDecimal minRating + ) { + Page resultPage = apiQueryService.searchApis( + page, + size, + providerCompany, + authType, + pricingType, + minRating + ); + + return ApiResponse.onPageSuccess(BaseSuccessCode.OK, resultPage); + } +} \ No newline at end of file From 7cdcba1b646c7ebe81887f88816f5caca24b4c0f Mon Sep 17 00:00:00 2001 From: SYDLK Date: Thu, 22 Jan 2026 21:37:34 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor[QueryDsl]:=20=ED=95=84=EC=9A=94X?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 -- .../domain/api/repository/ApiQueryDsl.java | 10 ------ .../api/repository/ApiQueryDslImpl.java | 35 ------------------- 3 files changed, 47 deletions(-) delete mode 100644 src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java delete mode 100644 src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java diff --git a/build.gradle b/build.gradle index e814551..9664f79 100644 --- a/build.gradle +++ b/build.gradle @@ -61,8 +61,6 @@ dependencies { testCompileOnly 'org.projectlombok:lombok:1.18.32' // 텍스트 롬복 추가 testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' - // 8. 보안 취약점 우회 - commons-lang3 transitive dependency override - implementation 'org.apache.commons:commons-lang3:3.17.1' } tasks.withType(Test).configureEach { diff --git a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java deleted file mode 100644 index a02e489..0000000 --- a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDsl.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.umc.apiwiki.domain.api.repository; - -import com.querydsl.core.types.Predicate; -import com.umc.apiwiki.domain.api.entity.Api; - -import java.util.List; - -public interface ApiQueryDsl { - List searchApi(Predicate predicate); -} diff --git a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java deleted file mode 100644 index 1a3518b..0000000 --- a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiQueryDslImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.umc.apiwiki.domain.api.repository; - -import com.querydsl.core.types.Predicate; -import com.querydsl.jpa.impl.JPAQueryFactory; -import com.umc.apiwiki.domain.api.entity.Api; -import com.umc.apiwiki.domain.api.entity.QApi; -import jakarta.persistence.EntityManager; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - - -@Service -@RequiredArgsConstructor -public class ApiQueryDslImpl implements ApiQueryDsl { - private final EntityManager em; - - // 검색 API - @Override - public List searchApi( - Predicate predicate - ){ - // JPA 세팅 - JPAQueryFactory queryFactory = new JPAQueryFactory(em); - - // Q 클래스 선언 - QApi api = QApi.api; - - return queryFactory - .selectFrom(api) - .where(predicate) - .fetch(); - } -} From 5744bd7404206c75ec4f130b8b72621320b3a400 Mon Sep 17 00:00:00 2001 From: SYDLK Date: Thu, 22 Jan 2026 21:39:22 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat[api]:=20API=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20-=20=EA=B0=80=EA=B2=A9=EC=A0=95=EC=B1=85,?= =?UTF-8?q?=20=EC=A0=9C=EA=B3=B5=ED=9A=8C=EC=82=AC,=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=ED=83=80=EC=9E=85,=20=EB=B3=84=EC=A0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/api/controller/ApiController.java | 13 ++++- .../domain/api/repository/ApiRepository.java | 3 +- .../domain/api/service/ApiQueryService.java | 52 +++++++++++-------- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java index 97777e6..c105edf 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java +++ b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java @@ -5,6 +5,9 @@ import com.umc.apiwiki.domain.api.enums.PricingType; import com.umc.apiwiki.domain.api.enums.ProviderCompany; import com.umc.apiwiki.domain.api.service.ApiQueryService; +import com.umc.apiwiki.global.apiPayload.ApiResponse; +import com.umc.apiwiki.global.apiPayload.code.GeneralSuccessCode; +import com.umc.apiwiki.global.apiPayload.dto.PageResponseDTO; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; @@ -12,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - import java.math.BigDecimal; @RestController @@ -26,20 +28,27 @@ public class ApiController { public ApiResponse> searchApis( @RequestParam int page, @RequestParam(required = false) Integer size, + @RequestParam(required = false) Long categoryId, +// @RequestParam(required = false) String q, // 검색어 +// @RequestParam(required = false, defaultValue = "latest") String sort, // 정렬 옵션 @RequestParam(required = false, name = "providers") ProviderCompany providerCompany, @RequestParam(required = false, name = "authTypes") AuthType authType, @RequestParam(required = false, name = "pricingTypes") PricingType pricingType, @RequestParam(required = false) BigDecimal minRating ) { + Page resultPage = apiQueryService.searchApis( page, size, + categoryId, +// q, +// sort, providerCompany, authType, pricingType, minRating ); - return ApiResponse.onPageSuccess(BaseSuccessCode.OK, resultPage); + return ApiResponse.onPageSuccess(GeneralSuccessCode.OK, resultPage); } } \ No newline at end of file diff --git a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java index 397a2e6..b702896 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java +++ b/src/main/java/com/umc/apiwiki/domain/api/repository/ApiRepository.java @@ -1,6 +1,7 @@ package com.umc.apiwiki.domain.api.repository; +import com.umc.apiwiki.domain.api.entity.Api; import org.springframework.data.jpa.repository.JpaRepository; -public interface ApiRepository extends JpaRepository , ApiQueryDsl { +public interface ApiRepository extends JpaRepository{ } diff --git a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java index 7a8b73d..0f224bb 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java +++ b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java @@ -6,14 +6,17 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.umc.apiwiki.domain.api.dto.ApiDTO; import com.umc.apiwiki.domain.api.entity.QApi; +import com.umc.apiwiki.domain.api.entity.QApiCategoriesMap; import com.umc.apiwiki.domain.api.enums.AuthType; import com.umc.apiwiki.domain.api.enums.PricingType; import com.umc.apiwiki.domain.api.enums.ProviderCompany; -import com.umc.apiwiki.domain.api.repository.ApiRepository; import com.umc.apiwiki.domain.community.entity.review.QApiReview; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.math.BigDecimal; @@ -23,13 +26,14 @@ @RequiredArgsConstructor public class ApiQueryService { - private final ApiRepository apiRepository; private final EntityManager entityManager; - // Explore / 필터용 API 목록 조회 public Page searchApis( int page, Integer size, + Long categoryId, +// String q, +// String sort, ProviderCompany providerCompany, AuthType authType, PricingType pricingType, @@ -38,27 +42,32 @@ public Page searchApis( QApi api = QApi.api; QApiReview review = QApiReview.apiReview; + QApiCategoriesMap map = QApiCategoriesMap.apiCategoriesMap; JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); - BooleanBuilder builder = new BooleanBuilder(); - // 동적 쿼리: 검색 조건 - if (providerCompany != null) { - builder.and(api.providerCompany.eq(providerCompany)); - } - if (authType != null) { - builder.and(api.authType.eq(authType)); - } - if (pricingType != null) { - builder.and(api.pricingType.eq(pricingType)); - } - if (minRating != null) { - builder.and(api.avgRating.goe(minRating)); + // 필터 조건 + if (providerCompany != null) builder.and(api.providerCompany.eq(providerCompany)); + if (authType != null) builder.and(api.authType.eq(authType)); + if (pricingType != null) builder.and(api.pricingType.eq(pricingType)); + if (minRating != null) builder.and(api.avgRating.goe(minRating)); + + // 카테고리 필터 (exists 서브쿼리) + if (categoryId != null) { + builder.and( + JPAExpressions.selectOne() + .from(map) + .where( + map.api.id.eq(api.id) + .and(map.category.id.eq(categoryId)) + ) + .exists() + ); } int pageSize = (size != null) ? size : 16; - Pageable pageable = PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); + Pageable pageable = PageRequest.of(page, pageSize); // reviewCount 서브쿼리 var reviewCountSubQuery = @@ -66,7 +75,7 @@ public Page searchApis( .from(review) .where(review.api.id.eq(api.id)); - // 목록 조회 + DTO 직접 생성 + // 목록 조회 List content = queryFactory .select(Projections.constructor( ApiDTO.ApiPreview.class, @@ -74,14 +83,15 @@ public Page searchApis( api.name, api.summary, api.avgRating, - reviewCountSubQuery, // Long - api.viewCounts, // Long (null 가능) + reviewCountSubQuery, + api.viewCounts, api.pricingType, api.authType, api.providerCompany )) .from(api) .where(builder) + .orderBy(api.createdAt.desc()) // 정렬 적용 .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); From 0a82c8da77a48c659111275fca28e760f2ee44eb Mon Sep 17 00:00:00 2001 From: SYDLK Date: Fri, 23 Jan 2026 01:50:18 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat[api]:=20API=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=B3=B8=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [- 향후 정렬(sort) 옵션 확장을 위한 코드 추가 - popular: 조회수순 - mostReviewed: 리뷰순 - latest: 최신순 - 향후 검색(q) 기능 확장을 위한 스텁 코드 추가 - name / summary / longDescription containsIgnoreCase 조건] --- .../domain/api/service/ApiQueryService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java index 0f224bb..3cb2115 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java +++ b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java @@ -66,6 +66,15 @@ public Page searchApis( ); } + // 검색 조건 +// if (q != null && !q.isBlank()) { +// builder.and( +// api.name.containsIgnoreCase(q) +// .or(api.summary.containsIgnoreCase(q)) +// .or(api.longDescription.containsIgnoreCase(q)) +// ); +// } + int pageSize = (size != null) ? size : 16; Pageable pageable = PageRequest.of(page, pageSize); @@ -75,6 +84,18 @@ public Page searchApis( .from(review) .where(review.api.id.eq(api.id)); + // 정렬 옵션 +// if (sort == null || sort.isBlank()) { +// sort = "latest"; +// } + +// var orderSpecifier = switch (sort) { +// case "popular" -> api.viewCounts.desc(); +// case "mostReviewed" -> reviewCountSubQuery.desc(); +// case "latest" -> api.createdAt.desc(); +// default -> api.createdAt.desc(); // 잘못된 값 방어 +// }; + // 목록 조회 List content = queryFactory .select(Projections.constructor( @@ -92,6 +113,7 @@ public Page searchApis( .from(api) .where(builder) .orderBy(api.createdAt.desc()) // 정렬 적용 +// .orderBy(orderSpecifier) // 기존 createdAt.desc() → 동적 정렬 .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); From 364150de62d736e4728cc3001dda1d10cc0b7faf Mon Sep 17 00:00:00 2001 From: SYDLK Date: Fri, 30 Jan 2026 22:09:09 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat[api]:=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20enum(ApiSortType,=20SortDirection)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/apiwiki/domain/api/enums/ApiSortType.java | 7 +++++++ .../com/umc/apiwiki/domain/api/enums/SortDirection.java | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 src/main/java/com/umc/apiwiki/domain/api/enums/ApiSortType.java create mode 100644 src/main/java/com/umc/apiwiki/domain/api/enums/SortDirection.java diff --git a/src/main/java/com/umc/apiwiki/domain/api/enums/ApiSortType.java b/src/main/java/com/umc/apiwiki/domain/api/enums/ApiSortType.java new file mode 100644 index 0000000..271bab2 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/enums/ApiSortType.java @@ -0,0 +1,7 @@ +package com.umc.apiwiki.domain.api.enums; + +public enum ApiSortType { + LATEST, + POPULAR, + MOST_REVIEWED +} diff --git a/src/main/java/com/umc/apiwiki/domain/api/enums/SortDirection.java b/src/main/java/com/umc/apiwiki/domain/api/enums/SortDirection.java new file mode 100644 index 0000000..a452de7 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/enums/SortDirection.java @@ -0,0 +1,6 @@ +package com.umc.apiwiki.domain.api.enums; + +public enum SortDirection { + ASC, + DESC +} From fbc75c4347c5edba8709444fdf9510c4a44561ca Mon Sep 17 00:00:00 2001 From: SYDLK Date: Fri, 30 Jan 2026 22:09:41 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor[api-query]:=20QueryDSL=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EA=B2=80=EC=83=89/=EC=A0=95=EB=A0=AC/?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=95=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/api/service/ApiQueryService.java | 184 +++++++++--------- .../apiwiki/global/config/QuerydslConfig.java | 18 ++ 2 files changed, 111 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/umc/apiwiki/global/config/QuerydslConfig.java diff --git a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java index 3cb2115..dd44ddd 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java +++ b/src/main/java/com/umc/apiwiki/domain/api/service/ApiQueryService.java @@ -1,39 +1,39 @@ package com.umc.apiwiki.domain.api.service; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; -import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import com.umc.apiwiki.domain.api.dto.ApiDTO; import com.umc.apiwiki.domain.api.entity.QApi; import com.umc.apiwiki.domain.api.entity.QApiCategoriesMap; -import com.umc.apiwiki.domain.api.enums.AuthType; -import com.umc.apiwiki.domain.api.enums.PricingType; -import com.umc.apiwiki.domain.api.enums.ProviderCompany; +import com.umc.apiwiki.domain.api.enums.*; import com.umc.apiwiki.domain.community.entity.review.QApiReview; -import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.*; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.querydsl.core.types.dsl.Expressions; + import java.math.BigDecimal; import java.util.List; + @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class ApiQueryService { - private final EntityManager entityManager; + private final JPAQueryFactory queryFactory; public Page searchApis( int page, Integer size, Long categoryId, -// String q, -// String sort, + String q, + ApiSortType sort, + SortDirection direction, ProviderCompany providerCompany, AuthType authType, PricingType pricingType, @@ -44,104 +44,106 @@ public Page searchApis( QApiReview review = QApiReview.apiReview; QApiCategoriesMap map = QApiCategoriesMap.apiCategoriesMap; - JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); BooleanBuilder builder = new BooleanBuilder(); - // 필터 조건 - if (providerCompany != null) builder.and(api.providerCompany.eq(providerCompany)); - if (authType != null) builder.and(api.authType.eq(authType)); - if (pricingType != null) builder.and(api.pricingType.eq(pricingType)); - if (minRating != null) builder.and(api.avgRating.goe(minRating)); + // 필터 + if (providerCompany != null) + builder.and(api.providerCompany.eq(providerCompany)); + + if (authType != null) + builder.and(api.authType.eq(authType)); + + if (pricingType != null) + builder.and(api.pricingType.eq(pricingType)); + + if (minRating != null) + builder.and(api.avgRating.goe(minRating)); + + // 검색 (CLOB 제외) + if (q != null && !q.isBlank()) { + String keyword = "%" + q.toLowerCase() + "%"; - // 카테고리 필터 (exists 서브쿼리) - if (categoryId != null) { builder.and( - JPAExpressions.selectOne() - .from(map) - .where( - map.api.id.eq(api.id) - .and(map.category.id.eq(categoryId)) - ) - .exists() + api.name.lower().like(keyword) + .or(api.summary.lower().like(keyword)) + .or(Expressions.stringTemplate( + "lower({0})", + api.providerCompany.stringValue() + ).like(keyword)) + .or(Expressions.stringTemplate( + "lower({0})", + api.authType.stringValue() + ).like(keyword)) + .or(Expressions.stringTemplate( + "lower({0})", + api.pricingType.stringValue() + ).like(keyword)) ); } - // 검색 조건 -// if (q != null && !q.isBlank()) { -// builder.and( -// api.name.containsIgnoreCase(q) -// .or(api.summary.containsIgnoreCase(q)) -// .or(api.longDescription.containsIgnoreCase(q)) -// ); -// } - int pageSize = (size != null) ? size : 16; Pageable pageable = PageRequest.of(page, pageSize); - // reviewCount 서브쿼리 - var reviewCountSubQuery = - JPAExpressions.select(review.count()) - .from(review) - .where(review.api.id.eq(api.id)); - - // 정렬 옵션 -// if (sort == null || sort.isBlank()) { -// sort = "latest"; -// } - -// var orderSpecifier = switch (sort) { -// case "popular" -> api.viewCounts.desc(); -// case "mostReviewed" -> reviewCountSubQuery.desc(); -// case "latest" -> api.createdAt.desc(); -// default -> api.createdAt.desc(); // 잘못된 값 방어 -// }; - - // 목록 조회 - List content = queryFactory + var query = queryFactory .select(Projections.constructor( ApiDTO.ApiPreview.class, api.id, api.name, api.summary, - api.avgRating, - reviewCountSubQuery, - api.viewCounts, + api.avgRating.coalesce(BigDecimal.ZERO), + review.id.count(), + api.viewCounts.coalesce(0L), api.pricingType, api.authType, api.providerCompany )) .from(api) - .where(builder) - .orderBy(api.createdAt.desc()) // 정렬 적용 -// .orderBy(orderSpecifier) // 기존 createdAt.desc() → 동적 정렬 - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - - // total count - Long total = queryFactory - .select(api.count()) - .from(api) - .where(builder) - .fetchOne(); - - long totalCount = (total != null) ? total : 0L; - - // null 안전처리 - content = content.stream() - .map(p -> new ApiDTO.ApiPreview( - p.apiId(), - p.name(), - p.summary(), - p.avgRating() != null ? p.avgRating() : BigDecimal.ZERO, - p.reviewCount() != null ? p.reviewCount() : 0L, - p.viewCounts() != null ? p.viewCounts() : 0L, - p.pricingType(), - p.authType(), - p.providerCompany() - )) - .toList(); + .leftJoin(review).on(review.api.id.eq(api.id)); + + // 카테고리 필터 + if (categoryId != null) { + query.join(map) + .on(map.api.id.eq(api.id)) + .where(map.category.id.eq(categoryId)); + } + + // 정렬 처리 + + boolean desc = direction == SortDirection.DESC; + + OrderSpecifier order = + switch (sort) { + + case POPULAR -> + desc ? api.viewCounts.desc() + : api.viewCounts.asc(); - return new PageImpl<>(content, pageable, totalCount); + case MOST_REVIEWED -> + desc ? review.id.count().desc() + : review.id.count().asc(); + + case LATEST -> + desc ? api.createdAt.desc() + : api.createdAt.asc(); + }; + + List content = + query + .where(builder) + .groupBy(api.id) + .orderBy(order) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long total = + queryFactory + .select(api.count()) + .from(api) + .where(builder) + .fetchOne(); + + return new PageImpl<>(content, pageable, total == null ? 0 : total); } -} \ No newline at end of file +} + diff --git a/src/main/java/com/umc/apiwiki/global/config/QuerydslConfig.java b/src/main/java/com/umc/apiwiki/global/config/QuerydslConfig.java new file mode 100644 index 0000000..0977bf3 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/global/config/QuerydslConfig.java @@ -0,0 +1,18 @@ +package com.umc.apiwiki.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} From 652e8d0f4f45ae15d84c9c1c9aafcb081800c574 Mon Sep 17 00:00:00 2001 From: SYDLK Date: Fri, 30 Jan 2026 22:09:59 +0900 Subject: [PATCH 10/11] =?UTF-8?q?feat[api-controller]:=20explore=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=ED=95=84=ED=84=B0=20=EB=B0=8F=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/api/controller/ApiController.java | 29 ++++++----- .../api/controller/ApiControllerDocs.java | 48 +++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java diff --git a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java index c105edf..e8ec1c9 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java +++ b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java @@ -1,13 +1,14 @@ package com.umc.apiwiki.domain.api.controller; import com.umc.apiwiki.domain.api.dto.ApiDTO; -import com.umc.apiwiki.domain.api.enums.AuthType; -import com.umc.apiwiki.domain.api.enums.PricingType; -import com.umc.apiwiki.domain.api.enums.ProviderCompany; +import com.umc.apiwiki.domain.api.enums.*; import com.umc.apiwiki.domain.api.service.ApiQueryService; import com.umc.apiwiki.global.apiPayload.ApiResponse; import com.umc.apiwiki.global.apiPayload.code.GeneralSuccessCode; import com.umc.apiwiki.global.apiPayload.dto.PageResponseDTO; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; @@ -20,29 +21,35 @@ @RestController @RequiredArgsConstructor @RequestMapping("/apis") -public class ApiController { +public class ApiController implements ApiControllerDocs{ private final ApiQueryService apiQueryService; @GetMapping public ApiResponse> searchApis( - @RequestParam int page, - @RequestParam(required = false) Integer size, + // page는 0-based 로 명시(Pageable 기준과 일치) + // 음수 방지 + @RequestParam(defaultValue = "0") @PositiveOrZero int page, + + // size는 null 허용하지만 값이 있으면 양수만 허용 + @RequestParam(required = false, defaultValue = "16") @Positive Integer size, @RequestParam(required = false) Long categoryId, -// @RequestParam(required = false) String q, // 검색어 -// @RequestParam(required = false, defaultValue = "latest") String sort, // 정렬 옵션 + @RequestParam(required = false) String q, // 검색어 + @RequestParam(required = false, defaultValue = "LATEST") ApiSortType sort, // 정렬 옵션 + @RequestParam(defaultValue = "ASC") SortDirection direction, // 정렬 방향 @RequestParam(required = false, name = "providers") ProviderCompany providerCompany, @RequestParam(required = false, name = "authTypes") AuthType authType, @RequestParam(required = false, name = "pricingTypes") PricingType pricingType, - @RequestParam(required = false) BigDecimal minRating + @RequestParam(required = false) @Positive @DecimalMax("5.0") BigDecimal minRating ) { Page resultPage = apiQueryService.searchApis( page, size, categoryId, -// q, -// sort, + q, + sort, + direction, providerCompany, authType, pricingType, diff --git a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java new file mode 100644 index 0000000..b9c94d8 --- /dev/null +++ b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java @@ -0,0 +1,48 @@ +package com.umc.apiwiki.domain.api.controller; + +import com.umc.apiwiki.domain.api.dto.ApiDTO; +import com.umc.apiwiki.domain.api.enums.*; +import com.umc.apiwiki.global.apiPayload.ApiResponse; +import com.umc.apiwiki.global.apiPayload.dto.PageResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; + +import java.math.BigDecimal; + +@Tag(name = "API 탐색", description = "Explore 페이지 API 목록 조회 및 필터") +public interface ApiControllerDocs { + + @Operation( + summary = "API 목록 조회 (Explore + 필터) By 악어", + description = """ + Explore 페이지용 API 목록 조회입니다. + + ▪ page는 0-based 입니다. + ▪ 기본 size = 16 + ▪ 모든 필터는 조합 가능합니다. + + [정렬 옵션] + - latest : 최신 등록순 (기본값) + - popular : 조회수 순 + - mostReviewed : 리뷰 많은 순 + """ + ) + ApiResponse> searchApis( + + @PositiveOrZero int page, + @Positive Integer size, + Long categoryId, + String q, + ApiSortType sort, + SortDirection direction, + ProviderCompany providerCompany, + AuthType authType, + PricingType pricingType, + @Positive + @DecimalMax("5.0") + BigDecimal minRating + ); +} \ No newline at end of file From 7931c3551e34a8604e30d3f93f961a7f6a4381c2 Mon Sep 17 00:00:00 2001 From: SYDLK Date: Sat, 31 Jan 2026 02:39:39 +0900 Subject: [PATCH 11/11] =?UTF-8?q?feat[api]:=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [GeneralErrorCode - 불필요한 세미콜론 삭제 ApiControllerDocs - 경로추가 ApiController - 어노테이션 병합] --- .../com/umc/apiwiki/domain/api/controller/ApiController.java | 4 ---- .../umc/apiwiki/domain/api/controller/ApiControllerDocs.java | 1 + .../umc/apiwiki/global/apiPayload/code/GeneralErrorCode.java | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java index 46c7e6b..4dd939e 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java +++ b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiController.java @@ -13,10 +13,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import org.springframework.data.domain.Page; -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; import java.math.BigDecimal; diff --git a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java index e0c5572..845521a 100644 --- a/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java +++ b/src/main/java/com/umc/apiwiki/domain/api/controller/ApiControllerDocs.java @@ -34,6 +34,7 @@ public interface ApiControllerDocs { - mostReviewed : 리뷰 많은 순 """ ) + @GetMapping("/apis") ApiResponse> searchApis( @PositiveOrZero int page, diff --git a/src/main/java/com/umc/apiwiki/global/apiPayload/code/GeneralErrorCode.java b/src/main/java/com/umc/apiwiki/global/apiPayload/code/GeneralErrorCode.java index baf4ee1..ad7fc63 100644 --- a/src/main/java/com/umc/apiwiki/global/apiPayload/code/GeneralErrorCode.java +++ b/src/main/java/com/umc/apiwiki/global/apiPayload/code/GeneralErrorCode.java @@ -39,7 +39,7 @@ public enum GeneralErrorCode implements BaseErrorCode{ INVALID_API_FILTER(HttpStatus.BAD_REQUEST, "API4003", "유효하지 않은 필터 또는 정렬 조건입니다."), API_NOT_PROCESSABLE(HttpStatus.BAD_REQUEST, "API4004", "요청을 처리할 수 없습니다."), API_FORBIDDEN(HttpStatus.FORBIDDEN, "API4005", "요청에 대한 권한이 없습니다."); - ; + private final HttpStatus status; private final String code;