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
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ plugins {
id 'io.spring.dependency-management' version '1.1.7'
}

ext {
springAiVersion = "1.1.2"
}

group = 'com.kt'
version = '0.0.1-SNAPSHOT'
description = 'Demo project for Spring Boot'
Expand Down Expand Up @@ -41,6 +45,9 @@ dependencies {
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
implementation 'io.micrometer:micrometer-registry-prometheus'

implementation platform("org.springframework.ai:spring-ai-bom:$springAiVersion")
implementation 'org.springframework.ai:spring-ai-starter-model-openai'

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'

implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
Expand Down Expand Up @@ -79,6 +86,12 @@ dependencies {
implementation 'com.bucket4j:bucket4j-caffeine:8.10.1'
}

dependencyManagement {
imports {
mavenBom "org.springframework.ai:spring-ai-bom:$springAiVersion"
}
}

// fixed mockito error
tasks.withType(Test).configureEach {
def mockitoCoreJar = configurations.testRuntimeClasspath.find {
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/kt/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@ public enum ErrorCode {
//wishlist
ALREADY_WISHLISTED(HttpStatus.BAD_REQUEST, "이미 찜한 상품입니다."),
WISHLIST_ADD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "찜 추가에 실패했습니다."),
NOT_FOUND_WISHLIST(HttpStatus.BAD_REQUEST, "찜하지 않은 상품입니다.")
NOT_FOUND_WISHLIST(HttpStatus.BAD_REQUEST, "찜하지 않은 상품입니다."),

//vector
NOT_FOUND_VECTOR_STORE(HttpStatus.BAD_REQUEST, "존재하지 않는 벡터 스토어입니다."),

//FAQ
NOT_FOUND_FAQ(HttpStatus.BAD_REQUEST, "존재하지 않는 자주 찾는 질문입니다.")
;

private final HttpStatus status;
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/kt/common/vector/VectorApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.kt.common.vector;

public interface VectorApi {
/**
*
* @param name 벡터 스토어 이름
* @param description 벡터 스토어 설명
* @return 생성된 벡터 스토어 ID
*/
String create(String name, String description);

String uploadFile(String vectorStoreId, byte[] json);

void delete(String vectorStoreId, String fileId);
}
3 changes: 1 addition & 2 deletions src/main/java/com/kt/config/SecurityConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
@RequiredArgsConstructor
public class SecurityConfiguration {


private static final String[] GET_PERMIT_ALL = {"/api/health/**", "/swagger-ui.html","/swagger-ui/**", "/v3/api-docs/**", "/actuator/**", "/payment-*.html", "/api/payments/client-key", "/*.css"};
private static final String[] GET_PERMIT_ALL = {"/api/health/**", "/swagger-ui.html","/swagger-ui/**", "/v3/api-docs/**", "/actuator/**", "/payment-*.html", "/api/payments/client-key", "/*.css", "/api/chats", "/api/chats/**"};
private static final String[] POST_PERMIT_ALL = {"/api/users/auth/signup", "/api/users/auth/login","/api/admin/users/auth/signup", "/api/admin/users/auth/login","/api/users/reissue", "/api/payments/confirm"};
private static final String[] PUT_PERMIT_ALL = {"/api/v1/public/**"};
private static final String[] PATCH_PERMIT_ALL = {"/api/v1/public/**"};
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/kt/controller/chat/ChatApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.kt.controller.chat;

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 com.kt.common.response.ApiResult;
import com.kt.service.chat.ChatService;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/chats")
@RequiredArgsConstructor
public class ChatApi {
private final ChatService chatService;

@GetMapping
public ApiResult<String> question(@RequestParam String query) {
return ApiResult.ok(chatService.questions(query));
}
}
46 changes: 46 additions & 0 deletions src/main/java/com/kt/controller/faq/AdminFAQController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.kt.controller.faq;

import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.kt.common.response.ApiResult;
import com.kt.dto.faq.FAQCreateRequest;
import com.kt.service.faq.FAQService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@PreAuthorize("hasRole('ADMIN')")
@Tag(name = "Admin FAQ", description = "FAQ 관리자용 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/admin/faqs")
public class AdminFAQController {
private final FAQService fAQService;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "FAQ 생성")
public ApiResult<Void> create(@RequestBody @Valid FAQCreateRequest request) throws Exception {
fAQService.create(request);
return ApiResult.ok();
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "FAQ 삭제")
public ApiResult<Void> delete(@PathVariable Long id) {
fAQService.delete(id);
return ApiResult.ok();
}

}
15 changes: 15 additions & 0 deletions src/main/java/com/kt/domain/faq/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.kt.domain.faq;

public enum Category {
ACCOUNT,
ADDRESS,
CART,
DISCOUNT,
MEMBERSHIP,
ORDER,
PAYMENT,
PRODUCT,
REVIEW,
VARIANT,
WISHLIST
}
30 changes: 30 additions & 0 deletions src/main/java/com/kt/domain/faq/FAQ.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.kt.domain.faq;

import com.kt.common.support.BaseEntity;

import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
public class FAQ extends BaseEntity {
private String title;
private String content;
@Enumerated(EnumType.STRING)
private Category category;
private String fileId;

public FAQ(String title, String content, Category category) {
this.title = title;
this.content = content;
this.category = category;
}

public void updateFileId(String fileId) {
this.fileId = fileId;
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/kt/domain/vector/Vector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.kt.domain.vector;

import com.kt.common.support.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Vector extends BaseEntity {
@Column(unique = true)
@Enumerated(EnumType.STRING)
private VectorType type;
private String storeId;
private String description;
private String name;
}
11 changes: 11 additions & 0 deletions src/main/java/com/kt/domain/vector/VectorType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.kt.domain.vector;

import java.util.List;

public enum VectorType {
FAQ;

public static List<VectorType> chatbotRange() {
return List.of(FAQ);
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/kt/dto/faq/FAQCreateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.kt.dto.faq;

import com.kt.domain.faq.Category;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record FAQCreateRequest(
@Schema(description = "FAQ 제목 (질문 형태 권장)", example = "환불은 얼마나 걸리나요?")
@NotBlank(message = "제목 입력은 필수입니다")
String title,
@Schema(description = "FAQ 답변 내용", example = "환불은 신청 후 영업일 2~3일 이내에 처리됩니다.")
@NotBlank(message = "내용 입력은 필수입니다")
String content,
@Schema(description = "FAQ 카테고리", example = "PAYMENT")
@NotNull(message = "카테고리 입력은 필수입니다")
Category category
) {
}
39 changes: 39 additions & 0 deletions src/main/java/com/kt/integration/config/OpenAIConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.kt.integration.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import com.kt.integration.openai.client.OpenAIClient;

import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
@ConditionalOnProperty(
prefix = "spring.ai.openai",
name = "enabled",
havingValue = "true"
)
public class OpenAIConfiguration {
@Bean
public ChatClient chatClient(ChatClient.Builder builder, BaseAdvisor openAICustomAdvisor) {
return builder
.defaultAdvisors(openAICustomAdvisor)
.build();
}

@Bean
public OpenAIClient openAIClient(RestClient.Builder builder) {
RestClient restClient = builder.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient)).build();

return factory.createClient(OpenAIClient.class);
}
}
Loading
Loading