Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,26 @@ 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'
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
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.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"

// 6. Swagger (SpringDoc OpenAPI - 스프링 부트 3.4와 호완을 위해 2.7 이상을 사용)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3'
Expand All @@ -53,25 +58,30 @@ 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'

}

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)
}
*/

// clean 태스크에 생성 폴더 삭제 로직 추가
clean.doLast {
file(querydslDir).deleteDir()
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
package com.umc.apiwiki.domain.api.controller;

import com.umc.apiwiki.domain.api.dto.ApiDTO;
import com.umc.apiwiki.domain.api.service.query.ApiQueryService;
import com.umc.apiwiki.domain.api.service.query.ApiSearchQueryService;
import com.umc.apiwiki.domain.api.service.query.ApiDetailQueryService;
import com.umc.apiwiki.domain.api.enums.*;
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.web.bind.annotation.*;
import org.springframework.data.domain.Page;

import java.math.BigDecimal;

@RestController
@RequiredArgsConstructor
public class ApiController implements ApiControllerDocs {
@RequestMapping("/api/v1")
public class ApiController implements ApiControllerDocs{

private final ApiQueryService apiQueryService;
private final ApiDetailQueryService apiDetailQueryService;
private final ApiSearchQueryService apiSearchQueryService;

@GetMapping("/{apiId}")
@GetMapping("/apis/{apiId}")
public ApiResponse<ApiDTO.ApiDetail> getApiDetail(@PathVariable Long apiId) {
return ApiResponse.onSuccess(
GeneralSuccessCode.OK,
apiQueryService.getApiDetail(apiId)
apiDetailQueryService.getApiDetail(apiId)
);
}
}

@GetMapping("/apis")
public ApiResponse<PageResponseDTO<ApiDTO.ApiPreview>> searchApis(
// 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") 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) @Positive @DecimalMax("5.0") BigDecimal minRating
) {

Page<ApiDTO.ApiPreview> resultPage = apiSearchQueryService.searchApis(
page,
size,
categoryId,
q,
sort,
direction,
providerCompany,
authType,
pricingType,
minRating
);

return ApiResponse.onPageSuccess(GeneralSuccessCode.OK, resultPage);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,57 @@
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 io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.math.BigDecimal;

@Tag(name = "API 탐색", description = "Explore 페이지 API 목록 조회 + 필터 + 상세 조회")
public interface ApiControllerDocs {

// API 목록 조회, 필터링
@Operation(
summary = "API 목록 조회 (Explore + 필터) By 악어",
description = """
Explore 페이지용 API 목록 조회입니다.

▪ page는 0-based 입니다.
▪ 기본 size = 16
▪ 모든 필터는 조합 가능합니다.

[정렬 옵션]
- latest : 최신 등록순 (기본값)
- popular : 조회수 순
- mostReviewed : 리뷰 많은 순
"""
)
@GetMapping("/apis")
ApiResponse<PageResponseDTO<ApiDTO.ApiPreview>> 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
);

// API 상세 조회
@Operation(
summary = "API 상세 조회 By 제인",
description = "API 개요 탭에서 한줄 설명, 카테고리 태그, 긴 설명 등을 반환합니다."
Expand All @@ -21,4 +64,4 @@ public interface ApiControllerDocs {
ApiResponse<ApiDTO.ApiDetail> getApiDetail(
@PathVariable Long apiId
);
}
}
28 changes: 22 additions & 6 deletions src/main/java/com/umc/apiwiki/domain/api/dto/ApiDTO.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
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;
import java.time.LocalDateTime;
import java.util.List;

public class ApiDTO {

public static record ApiDetail(
// Explore / 목록 조회 DTO by 악어
public record ApiPreview(
Long apiId,
String name,
String summary,
BigDecimal avgRating,
Long reviewCount,
Long viewCounts,
PricingType pricingType,
AuthType authType,
ProviderCompany providerCompany
) {}

// 상세 조회 DTO by 재인
public record ApiDetail(
Long apiId,
String name,
String summary,
Expand All @@ -18,12 +36,10 @@ public static record ApiDetail(
String logo,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
}
) {}

public static record CategoryItem(
public record CategoryItem(
Long categoryId,
String name
) {
}
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.umc.apiwiki.domain.api.enums;

public enum ApiSortType {
LATEST,
POPULAR,
MOST_REVIEWED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.umc.apiwiki.domain.api.enums;

public enum SortDirection {
ASC,
DESC
}
Original file line number Diff line number Diff line change
@@ -0,0 +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<Api, Long>{
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

@Service
@Transactional(readOnly = true)
public class ApiQueryService {
public class ApiDetailQueryService {

@PersistenceContext
private EntityManager em;
Expand Down
Loading
Loading