diff --git a/build.gradle b/build.gradle index 7c9e266..69730ad 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' + //gpt + implementation group: 'com.theokanning.openai-gpt3-java', name: 'client', version: '0.9.0' + implementation 'io.github.flashvayne:chatgpt-spring-boot-starter:1.0.4' + + //webflux + implementation 'org.springframework.boot:spring-boot-starter-webflux' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/finfellows/domain/chatbot/domain/Chatbot.java b/src/main/java/com/finfellows/domain/chatbot/domain/Chatbot.java new file mode 100644 index 0000000..a2055a7 --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatbot/domain/Chatbot.java @@ -0,0 +1,5 @@ +package com.finfellows.domain.chatbot.domain; + +public class Chatbot { + +} diff --git a/src/main/java/com/finfellows/domain/chatbot/domain/repository/ChatbotRepository.java b/src/main/java/com/finfellows/domain/chatbot/domain/repository/ChatbotRepository.java new file mode 100644 index 0000000..cbce09a --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatbot/domain/repository/ChatbotRepository.java @@ -0,0 +1,4 @@ +package com.finfellows.domain.chatbot.domain.repository; + +public interface ChatbotRepository { +} diff --git a/src/main/java/com/finfellows/domain/chatgpt/application/ChatGptService.java b/src/main/java/com/finfellows/domain/chatgpt/application/ChatGptService.java new file mode 100644 index 0000000..8704c09 --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/application/ChatGptService.java @@ -0,0 +1,59 @@ +package com.finfellows.domain.chatgpt.application; + +import io.github.flashvayne.chatgpt.service.ChatgptService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChatGptService { + + // 라이브러리 제공 + private final ChatgptService chatgptService; + + @Value("${chatgpt.api-key}") + private String apiKey; +// private final ObjectMapper objectMapper = new ObjectMapper() +// .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) +// .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE ); +// public Flux ask(ChatgptQuestionRequest chatGptQuestionRequest) throws JsonProcessingException { +// WebClient client = WebClient.builder() +// .baseUrl(ChatgptConfig.CHAT_URL) +// .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// .defaultHeader(ChatgptConfig.AUTHORIZATION, ChatgptConfig.BEARER + apiKey) +// .build(); +// +// List messages = new ArrayList<>(); +// messages.add(ChatGptMessage.builder() +// .role(ChatgptConfig.ROLE) +// .content(chatGptQuestionRequest.getQuestion()) +// .build()); +// +// ChatgptRequest chatGptRequest = new ChatgptRequest( +// ChatgptConfig.CHAT_MODEL, +// ChatgptConfig.MAX_TOKEN, +// ChatgptConfig.TEMPERATURE, +// ChatgptConfig.STREAM_TRUE, +// messages +// ); +// +// String requestValue = objectMapper.writeValueAsString(chatGptRequest); +// +// Flux eventStream = client.post() +// .bodyValue(requestValue) +// .accept(MediaType.TEXT_EVENT_STREAM) +// .retrieve() +// .bodyToFlux(String.class); +// +// return eventStream; +// } + + // 단답 답변 + public String getChatResponse(String prompt) { + System.out.print(apiKey); + // ChatGPT에 질문 전송 + return chatgptService.sendMessage(prompt); + } +} + diff --git a/src/main/java/com/finfellows/domain/chatgpt/config/ChatgptConfig.java b/src/main/java/com/finfellows/domain/chatgpt/config/ChatgptConfig.java new file mode 100644 index 0000000..ae35553 --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/config/ChatgptConfig.java @@ -0,0 +1,34 @@ +package com.finfellows.domain.chatgpt.config; + +import com.theokanning.openai.OpenAiService; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.Duration; + +@Slf4j +@Configuration +public class ChatgptConfig { + public static final String AUTHORIZATION = "Authorization"; + public static final String BEARER = "Bearer "; + public static final String CHAT_MODEL = "gpt-3.5-turbo"; + public static final Integer MAX_TOKEN = 300; + public static final Boolean STREAM_TRUE = true; + public static final Boolean STREAM_FALSE = false; + public static final String ROLE = "user"; + public static final Double TEMPERATURE = 0.6; + public static final String MEDIA_TYPE = "application/json; charset=UTF-8"; + public static final String CHAT_URL = "https://api.openai.com/v1/chat/completions"; + + @Value("${chatgpt.api-key}") + private String token; + + @Bean + public OpenAiService openAiService() { + log.info("token: {}을 활용한 OpenApiService를 생성합니다!", token); + return new OpenAiService(token, Duration.ofSeconds(60)); + } +} diff --git a/src/main/java/com/finfellows/domain/chatgpt/domain/ChatGptMessage.java b/src/main/java/com/finfellows/domain/chatgpt/domain/ChatGptMessage.java new file mode 100644 index 0000000..3fb0533 --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/domain/ChatGptMessage.java @@ -0,0 +1,11 @@ +package com.finfellows.domain.chatgpt.domain; + +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class ChatGptMessage { + private String role; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/chatgpt/dto/request/ChatgptQuestionRequest.java b/src/main/java/com/finfellows/domain/chatgpt/dto/request/ChatgptQuestionRequest.java new file mode 100644 index 0000000..d96795e --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/dto/request/ChatgptQuestionRequest.java @@ -0,0 +1,12 @@ +package com.finfellows.domain.chatgpt.dto.request; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Getter +@NoArgsConstructor +public class ChatgptQuestionRequest implements Serializable { + private String question; +} diff --git a/src/main/java/com/finfellows/domain/chatgpt/dto/request/ChatgptRequest.java b/src/main/java/com/finfellows/domain/chatgpt/dto/request/ChatgptRequest.java new file mode 100644 index 0000000..6f526eb --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/dto/request/ChatgptRequest.java @@ -0,0 +1,37 @@ +package com.finfellows.domain.chatgpt.dto.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.finfellows.domain.chatgpt.domain.ChatGptMessage; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.*; + +@Getter +@NoArgsConstructor +//chatGPT에 요청할 DTO Format +public class ChatgptRequest implements Serializable { + private String model; + @JsonProperty("max_tokens") + private Integer maxTokens; + private Double temperature; + private Boolean stream; + private List messages; + + //@JsonProperty("top_p") + //private Double topP; + + @Builder + public ChatgptRequest(String model, Integer maxTokens, Double temperature, + Boolean stream, List messages + /*,Double topP*/) { + this.model = model; + this.maxTokens = maxTokens; + this.temperature = temperature; + this.stream = stream; + this.messages = messages; + //this.topP = topP; + } +} diff --git a/src/main/java/com/finfellows/domain/chatgpt/dto/response/ChatgptResponse.java b/src/main/java/com/finfellows/domain/chatgpt/dto/response/ChatgptResponse.java new file mode 100644 index 0000000..dcab9bc --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/dto/response/ChatgptResponse.java @@ -0,0 +1,21 @@ +package com.finfellows.domain.chatgpt.dto.response; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import java.util.*; + +@Getter +@NoArgsConstructor +//GPT 답변에 대한 DTO +public class ChatgptResponse { + private String id; + private String model; + private List choices; + + @Getter + @Setter + public static class Usage { + private int totalTokens; + } +} diff --git a/src/main/java/com/finfellows/domain/chatgpt/presentation/ChatGptController.java b/src/main/java/com/finfellows/domain/chatgpt/presentation/ChatGptController.java new file mode 100644 index 0000000..b10657b --- /dev/null +++ b/src/main/java/com/finfellows/domain/chatgpt/presentation/ChatGptController.java @@ -0,0 +1,37 @@ +package com.finfellows.domain.chatgpt.presentation; + +import com.finfellows.domain.chatgpt.application.ChatGptService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +@RestController +@RequestMapping("/api/v1/chat-gpt") +@RequiredArgsConstructor +@Slf4j +public class ChatGptController { + private final ChatGptService chatgptService; + +// @PostMapping(value="/ask-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) +// public Flux ask(Locale locale, +// HttpServletRequest request, +// HttpServletResponse response, +// @RequestBody ChatgptQuestionRequest chatGptQuestionRequest){ +// try { +// return chatgptService.ask(chatGptQuestionRequest); +// }catch (JsonProcessingException je){ +// log.error(je.getMessage()); +// return Flux.empty(); +// } +// } + + // 단답 테스트 + // https://yjkim-dev.tistory.com/56 + @PostMapping("") + public String test(@RequestBody String question) { + return chatgptService.getChatResponse(question); + } +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/comment/application/CommentService.java b/src/main/java/com/finfellows/domain/comment/application/CommentService.java new file mode 100644 index 0000000..da5d479 --- /dev/null +++ b/src/main/java/com/finfellows/domain/comment/application/CommentService.java @@ -0,0 +1,84 @@ +package com.finfellows.domain.comment.application; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.finfellows.domain.chatgpt.domain.ChatGptMessage; +import com.finfellows.domain.chatgpt.application.ChatGptService; +import com.finfellows.domain.chatgpt.config.ChatgptConfig; +import com.finfellows.domain.chatgpt.dto.request.ChatgptQuestionRequest; +import com.finfellows.domain.chatgpt.dto.request.ChatgptRequest; +import com.finfellows.domain.chatgpt.dto.response.ChatgptResponse; +import com.finfellows.domain.comment.domain.repository.CommentRepository; +import com.finfellows.domain.user.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.*; + +@Service +@RequiredArgsConstructor +public class CommentService { + private final UserRepository userRepository; + private final ChatGptService chatGptService; + private final CommentRepository commentRepository; + + @Autowired + private RestTemplate restTemplate; + + @Value("${chatgpt.api-key}") + private String apiKey; + private final ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + + // gpt 단답 + public HttpEntity buildHttpEntity(ChatgptRequest chatGptRequest) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.parseMediaType(ChatgptConfig.MEDIA_TYPE)); + httpHeaders.add(ChatgptConfig.AUTHORIZATION, ChatgptConfig.BEARER + apiKey); + return new HttpEntity<>(chatGptRequest, httpHeaders); + } + + // gpt 단답 + public ChatgptResponse getResponse(HttpEntity chatGptRequestHttpEntity) { + + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setConnectTimeout(60000); + //답변이 길어질 경우 TimeOut Error가 발생하니 1분정도 설정해줍니다. + requestFactory.setReadTimeout(60 * 1000); // 1min = 60 sec * 1,000ms + restTemplate.setRequestFactory(requestFactory); + + ResponseEntity responseEntity = restTemplate.postForEntity( + ChatgptConfig.CHAT_URL, + chatGptRequestHttpEntity, + ChatgptResponse.class); + + return responseEntity.getBody(); + } + + // gpt 단답 + public ChatgptResponse askQuestion(ChatgptQuestionRequest questionRequest) { + List messages = new ArrayList<>(); + messages.add(ChatGptMessage.builder() + .role(ChatgptConfig.ROLE) + .content(questionRequest.getQuestion()) + .build()); + return this.getResponse( + this.buildHttpEntity( + new ChatgptRequest( + ChatgptConfig.CHAT_MODEL, + ChatgptConfig.MAX_TOKEN, + ChatgptConfig.TEMPERATURE, + ChatgptConfig.STREAM_FALSE, + messages + ) + ) + ); + } +} diff --git a/src/main/java/com/finfellows/domain/comment/domain/Comment.java b/src/main/java/com/finfellows/domain/comment/domain/Comment.java new file mode 100644 index 0000000..00c31c2 --- /dev/null +++ b/src/main/java/com/finfellows/domain/comment/domain/Comment.java @@ -0,0 +1,33 @@ +package com.finfellows.domain.comment.domain; + +import com.finfellows.domain.common.BaseEntity; +import com.finfellows.domain.user.domain.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="Comment") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Comment extends BaseEntity { + + //질문 내용 저장 칼럼 필요함. + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="comment_id", updatable = false, nullable = false, unique = true) + private Long commentId; + + @Column(name="comment_content") + private String commentContent; + + @ManyToOne + @JoinColumn(name="user_id") + private User userId; + + @Builder + public Comment(Long commentId, String commentContent, User userId){ + this.commentId=commentId; + this.commentContent=commentContent; + this.userId=userId; + } +} diff --git a/src/main/java/com/finfellows/domain/comment/domain/repository/CommentRepository.java b/src/main/java/com/finfellows/domain/comment/domain/repository/CommentRepository.java new file mode 100644 index 0000000..ec86eae --- /dev/null +++ b/src/main/java/com/finfellows/domain/comment/domain/repository/CommentRepository.java @@ -0,0 +1,7 @@ +package com.finfellows.domain.comment.domain.repository; + +import com.finfellows.domain.comment.domain.Comment; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { +} diff --git a/src/main/java/com/finfellows/domain/comment/presentation/CommentController.java b/src/main/java/com/finfellows/domain/comment/presentation/CommentController.java new file mode 100644 index 0000000..4bcc573 --- /dev/null +++ b/src/main/java/com/finfellows/domain/comment/presentation/CommentController.java @@ -0,0 +1,9 @@ +package com.finfellows.domain.comment.presentation; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class CommentController { +} diff --git a/src/main/java/com/finfellows/domain/educontent/application/EduContentService.java b/src/main/java/com/finfellows/domain/educontent/application/EduContentService.java new file mode 100644 index 0000000..437605e --- /dev/null +++ b/src/main/java/com/finfellows/domain/educontent/application/EduContentService.java @@ -0,0 +1,86 @@ +package com.finfellows.domain.educontent.application; + +import com.finfellows.domain.educontent.domain.EduContent; +import com.finfellows.domain.educontent.domain.repository.EduContentRepository; +import com.finfellows.domain.educontent.dto.request.EduContentRequest; +import com.finfellows.domain.educontent.dto.response.EduContentResponse; +import com.finfellows.domain.post.domain.Post; +import com.finfellows.domain.post.domain.repository.PostRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EduContentService { + private final EduContentRepository eduContentRepository; + private final PostRepository postRepository; + + @Transactional + public EduContent createEduContent(EduContentResponse request) { + Post post = new Post(); + postRepository.save(post); + + // 빌더 패턴을 사용하여 EduContent 생성하면서 Post 엔터티를 설정 + EduContent eduContent = EduContent.builder() + .title(request.getTitle()) + .content(request.getContent()) + .post(post) // Post 엔터티를 설정 + .build(); + + // EduContent 저장 + EduContent savedContent = eduContentRepository.save(eduContent); + return savedContent; + } + + public List getAllEduContents() { + List eduContents = eduContentRepository.findAll(); + return eduContents.stream() + .map(eduContent -> EduContentResponse.builder() + .id(eduContent.getId()) + .title(eduContent.getTitle()) + .content(eduContent.getContent()) + .build()) + .collect(Collectors.toList()); + } + + public EduContentResponse getEduContent(Long id) { + EduContent eduContent = eduContentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("EduContent not found with id: " + id)); + + return EduContentResponse.builder() + .id(eduContent.getId()) + .title(eduContent.getTitle()) + .content(eduContent.getContent()) + .build(); + } + + @Transactional + public void deleteEduContent(Long id) { + EduContent eduContent = eduContentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("EduContent not found with id: " + id)); + + eduContentRepository.delete(eduContent); + } + + @Transactional + public EduContentResponse updateEduContent(Long id, EduContentRequest request) { + EduContent eduContent = eduContentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("EduContent not found with id: " + id)); + + eduContent.updateContent(request.getTitle(), request.getContent()); + + EduContent updatedContent = eduContentRepository.save(eduContent); + + return EduContentResponse.builder() + .id(updatedContent.getId()) + .title(updatedContent.getTitle()) + .content(updatedContent.getContent()) + .build(); + } +} diff --git a/src/main/java/com/finfellows/domain/educontent/domain/EduContent.java b/src/main/java/com/finfellows/domain/educontent/domain/EduContent.java index 17aeb0d..420223b 100644 --- a/src/main/java/com/finfellows/domain/educontent/domain/EduContent.java +++ b/src/main/java/com/finfellows/domain/educontent/domain/EduContent.java @@ -23,19 +23,25 @@ public class EduContent extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="post_id") - private Post post_id; + private Post post; - @Column(name="sequence", nullable = false) - private Long sequence; + + @Column(name="title") + private String title; @Column(name="content") private String content; @Builder - public EduContent(Post post_id, Long sequence, String content){ - this.post_id=post_id; - this.sequence=sequence; + public EduContent(Post post, String title, String content){ + this.post=post; + this.title=title; this.content=content; } + public void updateContent(String title, String content) { + this.title = title; + this.content = content; + } + } diff --git a/src/main/java/com/finfellows/domain/educontent/domain/repository/EduContentRepository.java b/src/main/java/com/finfellows/domain/educontent/domain/repository/EduContentRepository.java index cc97b6a..a2fc8c0 100644 --- a/src/main/java/com/finfellows/domain/educontent/domain/repository/EduContentRepository.java +++ b/src/main/java/com/finfellows/domain/educontent/domain/repository/EduContentRepository.java @@ -2,7 +2,8 @@ import com.finfellows.domain.educontent.domain.EduContent; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; - +@Repository public interface EduContentRepository extends JpaRepository { } diff --git a/src/main/java/com/finfellows/domain/educontent/dto/request/EduContentRequest.java b/src/main/java/com/finfellows/domain/educontent/dto/request/EduContentRequest.java new file mode 100644 index 0000000..130e9e0 --- /dev/null +++ b/src/main/java/com/finfellows/domain/educontent/dto/request/EduContentRequest.java @@ -0,0 +1,16 @@ +package com.finfellows.domain.educontent.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EduContentRequest { + private Long id; + private String title; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/educontent/dto/response/EduContentResponse.java b/src/main/java/com/finfellows/domain/educontent/dto/response/EduContentResponse.java new file mode 100644 index 0000000..d711383 --- /dev/null +++ b/src/main/java/com/finfellows/domain/educontent/dto/response/EduContentResponse.java @@ -0,0 +1,16 @@ +package com.finfellows.domain.educontent.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EduContentResponse { + private Long id; + private String title; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/educontent/presentation/EduContentController.java b/src/main/java/com/finfellows/domain/educontent/presentation/EduContentController.java new file mode 100644 index 0000000..c9fa017 --- /dev/null +++ b/src/main/java/com/finfellows/domain/educontent/presentation/EduContentController.java @@ -0,0 +1,74 @@ +package com.finfellows.domain.educontent.presentation; + +import com.finfellows.domain.educontent.application.EduContentService; +import com.finfellows.domain.educontent.domain.EduContent; +import com.finfellows.domain.educontent.dto.request.EduContentRequest; +import com.finfellows.domain.educontent.dto.response.EduContentResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/learn/edu") +@Tag(name = "EduContent", description = "EduContent API") +public class EduContentController { + private final EduContentService eduContentService; + + @Operation(summary = "교육콘텐츠 저장", description = "교육콘텐츠를 저장합니다.") + @ApiResponse(responseCode = "201", description = "교육콘텐츠 저장 성공", content = { + @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = EduContentResponse.class))) + }) + @PostMapping("") + public ResponseEntity saveEduContent(@RequestBody EduContentResponse request) { + EduContent response = eduContentService.createEduContent(request); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + @Operation(summary = "교육콘텐츠 전체 목록 조회", description = "교육콘텐츠 전체 목록을 조회합니다.") + @ApiResponse(responseCode = "200", description = "E교육콘텐츠 목록 조회 성공", content = { + @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = EduContentResponse.class))) + }) + @GetMapping + public ResponseEntity> getAllEduContents() { + List responseList = eduContentService.getAllEduContents(); + return new ResponseEntity<>(responseList, HttpStatus.OK); + } + + @Operation(summary = "교육콘텐츠 상세 내용 조회", description = "교육콘텐츠 상세 내용을 조회합니다.") + @ApiResponse(responseCode = "200", description = "교육콘텐츠 조회 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = EduContentResponse.class)) + }) + @GetMapping("/{id}") + public ResponseEntity getEduContent(@PathVariable Long id) { + EduContentResponse response = eduContentService.getEduContent(id); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @Operation(summary = "교육콘텐츠 삭제", description = "교육콘텐츠를 삭제합니다.") + @ApiResponse(responseCode = "204", description = "교육콘텐츠 삭제 성공") + @DeleteMapping("/{id}") + public ResponseEntity deleteEduContent(@PathVariable Long id) { + eduContentService.deleteEduContent(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Operation(summary = "교육콘텐츠 수정", description = "교육콘텐츠를 수정합니다.") + @ApiResponse(responseCode = "200", description = "교육콘텐츠 수정 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = EduContentResponse.class)) + }) + @PatchMapping("/{id}") + public ResponseEntity updateEduContent(@PathVariable Long id, @RequestBody EduContentRequest request) { + EduContentResponse updatedContent = eduContentService.updateEduContent(id, request); + return new ResponseEntity<>(updatedContent, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/newscontent/application/NewsContentService.java b/src/main/java/com/finfellows/domain/newscontent/application/NewsContentService.java new file mode 100644 index 0000000..c6e0891 --- /dev/null +++ b/src/main/java/com/finfellows/domain/newscontent/application/NewsContentService.java @@ -0,0 +1,86 @@ +package com.finfellows.domain.newscontent.application; + +import com.finfellows.domain.newscontent.domain.NewsContent; +import com.finfellows.domain.newscontent.domain.repository.NewsContentRepository; +import com.finfellows.domain.newscontent.dto.request.NewsContentRequest; +import com.finfellows.domain.newscontent.dto.response.NewsContentResponse; +import com.finfellows.domain.post.domain.Post; +import com.finfellows.domain.post.domain.repository.PostRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NewsContentService { + private final NewsContentRepository newsContentRepository; + private final PostRepository postRepository; + + @Transactional + public NewsContent createNewsContent(NewsContentResponse request) { + Post post = new Post(); + postRepository.save(post); + + // 빌더 패턴을 사용하여 NewsContent 생성하면서 Post 엔터티를 설정 + NewsContent newsContent = NewsContent.builder() + .title(request.getTitle()) + .content(request.getContent()) + .post(post) // Post 엔터티를 설정 + .build(); + + // NewsContent 저장 + NewsContent savedContent = newsContentRepository.save(newsContent); + return savedContent; + } + + public List getAllNewsContents() { + List newsContents = newsContentRepository.findAll(); + return newsContents.stream() + .map(newsContent -> NewsContentResponse.builder() + .id(newsContent.getId()) + .title(newsContent.getTitle()) + .content(newsContent.getContent()) + .build()) + .collect(Collectors.toList()); + } + + public NewsContentResponse getNewsContent(Long id) { + NewsContent newsContent = newsContentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("NewsContent not found with id: " + id)); + + return NewsContentResponse.builder() + .id(newsContent.getId()) + .title(newsContent.getTitle()) + .content(newsContent.getContent()) + .build(); + } + + @Transactional + public void deleteNewsContent(Long id) { + NewsContent newsContent = newsContentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("NewsContent not found with id: " + id)); + + newsContentRepository.delete(newsContent); + } + + @Transactional + public NewsContentResponse updateNewsContent(Long id, NewsContentRequest request) { + NewsContent newsContent = newsContentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("NewsContent not found with id: " + id)); + + newsContent.updateContent(request.getTitle(), request.getContent()); + + NewsContent updatedContent = newsContentRepository.save(newsContent); + + return NewsContentResponse.builder() + .id(updatedContent.getId()) + .title(updatedContent.getTitle()) + .content(updatedContent.getContent()) + .build(); + } +} diff --git a/src/main/java/com/finfellows/domain/newscontent/domain/NewsContent.java b/src/main/java/com/finfellows/domain/newscontent/domain/NewsContent.java new file mode 100644 index 0000000..5d6006c --- /dev/null +++ b/src/main/java/com/finfellows/domain/newscontent/domain/NewsContent.java @@ -0,0 +1,43 @@ +package com.finfellows.domain.newscontent.domain; + +import com.finfellows.domain.common.BaseEntity; +import com.finfellows.domain.post.domain.Post; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; + +@Entity +@Table(name="NewsContent") +@NoArgsConstructor +@Getter +@Where(clause = "status = 'ACTIVE'") +public class NewsContent extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id", updatable = false) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="post_id") + private Post post; + + @Column(name="title") + private String title; + + @Column(name="content") + private String content; + + @Builder + public NewsContent(Post post, String title, String content){ + this.post=post; + this.title=title; + this.content=content; + } + + public void updateContent(String title, String content) { + this.title = title; + this.content = content; + } +} diff --git a/src/main/java/com/finfellows/domain/newscontent/domain/repository/NewsContentRepository.java b/src/main/java/com/finfellows/domain/newscontent/domain/repository/NewsContentRepository.java new file mode 100644 index 0000000..150ccd4 --- /dev/null +++ b/src/main/java/com/finfellows/domain/newscontent/domain/repository/NewsContentRepository.java @@ -0,0 +1,9 @@ +package com.finfellows.domain.newscontent.domain.repository; + +import com.finfellows.domain.newscontent.domain.NewsContent; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface NewsContentRepository extends JpaRepository { +} diff --git a/src/main/java/com/finfellows/domain/newscontent/dto/request/NewsContentRequest.java b/src/main/java/com/finfellows/domain/newscontent/dto/request/NewsContentRequest.java new file mode 100644 index 0000000..40066a1 --- /dev/null +++ b/src/main/java/com/finfellows/domain/newscontent/dto/request/NewsContentRequest.java @@ -0,0 +1,16 @@ +package com.finfellows.domain.newscontent.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class NewsContentRequest { + private Long id; + private String title; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/newscontent/dto/response/NewsContentResponse.java b/src/main/java/com/finfellows/domain/newscontent/dto/response/NewsContentResponse.java new file mode 100644 index 0000000..584e34c --- /dev/null +++ b/src/main/java/com/finfellows/domain/newscontent/dto/response/NewsContentResponse.java @@ -0,0 +1,16 @@ +package com.finfellows.domain.newscontent.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class NewsContentResponse { + private Long id; + private String title; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/newscontent/presentation/NewsContentController.java b/src/main/java/com/finfellows/domain/newscontent/presentation/NewsContentController.java new file mode 100644 index 0000000..db28bc3 --- /dev/null +++ b/src/main/java/com/finfellows/domain/newscontent/presentation/NewsContentController.java @@ -0,0 +1,74 @@ +package com.finfellows.domain.newscontent.presentation; + +import com.finfellows.domain.newscontent.application.NewsContentService; +import com.finfellows.domain.newscontent.domain.NewsContent; +import com.finfellows.domain.newscontent.dto.request.NewsContentRequest; +import com.finfellows.domain.newscontent.dto.response.NewsContentResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/learn/news") +@Tag(name = "NewsContent", description = "NewsContent API") +public class NewsContentController { + private final NewsContentService newsContentService; + + @Operation(summary = "뉴스콘텐츠 저장", description = "뉴스콘텐츠를 저장합니다.") + @ApiResponse(responseCode = "201", description = "뉴스콘텐츠 저장 성공", content = { + @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = NewsContentResponse.class))) + }) + @PostMapping("") + public ResponseEntity saveNewsContent(@RequestBody NewsContentResponse request) { + NewsContent response = newsContentService.createNewsContent(request); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + @Operation(summary = "뉴스콘텐츠 전체 목록 조회", description = "뉴스콘텐츠 전체 목록을 조회합니다.") + @ApiResponse(responseCode = "200", description = "뉴스콘텐츠 목록 조회 성공", content = { + @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = NewsContentResponse.class))) + }) + @GetMapping + public ResponseEntity> getAllNewsContents() { + List responseList = newsContentService.getAllNewsContents(); + return new ResponseEntity<>(responseList, HttpStatus.OK); + } + + @Operation(summary = "뉴스콘텐츠 상세 내용 조회", description = "뉴스콘텐츠 상세 내용을 조회합니다.") + @ApiResponse(responseCode = "200", description = "뉴스콘텐츠 조회 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = NewsContentResponse.class)) + }) + @GetMapping("/{id}") + public ResponseEntity getNewsContent(@PathVariable Long id) { + NewsContentResponse response = newsContentService.getNewsContent(id); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @Operation(summary = "뉴스콘텐츠 삭제", description = "뉴스콘텐츠를 삭제합니다.") + @ApiResponse(responseCode = "204", description = "뉴스콘텐츠 삭제 성공") + @DeleteMapping("/{id}") + public ResponseEntity deleteNewsContent(@PathVariable Long id) { + newsContentService.deleteNewsContent(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Operation(summary = "뉴스콘텐츠 수정", description = "뉴스콘텐츠를 수정합니다.") + @ApiResponse(responseCode = "200", description = "뉴스콘텐츠 수정 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = NewsContentResponse.class)) + }) + @PatchMapping("/{id}") + public ResponseEntity updateNewsContent(@PathVariable Long id, @RequestBody NewsContentRequest request) { + NewsContentResponse updatedContent = newsContentService.updateNewsContent(id, request); + return new ResponseEntity<>(updatedContent, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/post/application/ContentService.java b/src/main/java/com/finfellows/domain/post/application/ContentService.java new file mode 100644 index 0000000..b73ef7a --- /dev/null +++ b/src/main/java/com/finfellows/domain/post/application/ContentService.java @@ -0,0 +1,88 @@ +package com.finfellows.domain.post.application; + +import com.finfellows.domain.post.domain.Content; +import com.finfellows.domain.post.domain.Post; +import com.finfellows.domain.post.domain.repository.ContentRepository; +import com.finfellows.domain.post.domain.repository.PostRepository; +import com.finfellows.domain.post.dto.request.ContentRequest; +import com.finfellows.domain.post.dto.response.ContentResponse; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ContentService { + private final ContentRepository contentRepository; + private final PostRepository postRepository; + + @Transactional + public Content createContent(ContentResponse request) { + Post post = new Post(); + postRepository.save(post); + + // 빌더 패턴을 사용하여 Content 생성하면서 Post 엔터티를 설정 + Content content = Content.builder() + .title(request.getTitle()) + .content(request.getContent()) + .post_id(post) // Post 엔터티를 설정 + .build(); + + // Content 저장 + Content savedContent = contentRepository.save(content); + return savedContent; + } + + public List getAllContents() { + List contents = contentRepository.findAll(); + return contents.stream() + .map(content -> ContentResponse.builder() + .id(content.getId()) + .created_at(content.getPost_id().getCreatedAt()) // 수정된 부분 + .title(content.getTitle()) + .content(content.getContent()) + .build()) + .collect(Collectors.toList()); + } + + public ContentResponse getContent(Long id) { + Content content = contentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("Content not found with id: " + id)); + + return ContentResponse.builder() + .id(content.getId()) + .created_at(content.getPost_id().getCreatedAt()) + .title(content.getTitle()) + .content(content.getContent()) + .build(); + } + + @Transactional + public void deleteContent(Long id) { + Content content = contentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("Content not found with id: " + id)); + + contentRepository.delete(content); + } + + @Transactional + public ContentResponse updateContent(Long id, ContentRequest request) { + Content content = contentRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("Content not found with id: " + id)); + + content.updateContent(request.getTitle(), request.getContent()); + + Content updatedContent = contentRepository.save(content); + + return ContentResponse.builder() + .id(updatedContent.getId()) + .title(updatedContent.getTitle()) + .content(updatedContent.getContent()) + .build(); + } +} diff --git a/src/main/java/com/finfellows/domain/post/domain/Content.java b/src/main/java/com/finfellows/domain/post/domain/Content.java index d6a55d7..c5f3db5 100644 --- a/src/main/java/com/finfellows/domain/post/domain/Content.java +++ b/src/main/java/com/finfellows/domain/post/domain/Content.java @@ -1,39 +1,47 @@ -package com.finfellows.domain.post.domain; - -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Where; - -@Entity -@Table(name="Content") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Where(clause = "status = 'ACTIVE'") -public class Content { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", updatable = false) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="post_id") - private Post post_id; - - @Column(name="sequence", nullable = false) - private Long sequence; - - @Column(name="content") - private String content; - - @Builder - public Content(Post post_id, Long sequence, String content){ - this.post_id=post_id; - this.sequence=sequence; - this.content=content; - } - -} + +package com.finfellows.domain.post.domain; + +import com.finfellows.domain.common.Status; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; + +import java.time.LocalDate; + +@Entity +@Table(name="Content") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Where(clause = "status = 'ACTIVE'") +public class Content { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", updatable = false) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="post_id") + private Post post_id; + + @Column(name="title") + private String title; + + @Column(name="content") + private String content; + + + @Builder + public Content(Post post_id, String title, String content){ + this.post_id=post_id; + this.title=title; + this.content=content; + } + + public void updateContent(String title, String content) { + this.title = title; + this.content = content; + } +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/post/domain/Post.java b/src/main/java/com/finfellows/domain/post/domain/Post.java index 6b9e988..5231c18 100644 --- a/src/main/java/com/finfellows/domain/post/domain/Post.java +++ b/src/main/java/com/finfellows/domain/post/domain/Post.java @@ -11,7 +11,7 @@ @Entity @Table(name="Post") -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PUBLIC) @Getter @Where(clause = "status = 'ACTIVE'") public class Post extends BaseEntity { diff --git a/src/main/java/com/finfellows/domain/post/domain/repository/ContentRepository.java b/src/main/java/com/finfellows/domain/post/domain/repository/ContentRepository.java index eafd9f6..859487e 100644 --- a/src/main/java/com/finfellows/domain/post/domain/repository/ContentRepository.java +++ b/src/main/java/com/finfellows/domain/post/domain/repository/ContentRepository.java @@ -1,7 +1,9 @@ -package com.finfellows.domain.post.domain.repository; - -import com.finfellows.domain.post.domain.Post; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ContentRepository extends JpaRepository { -} +package com.finfellows.domain.post.domain.repository; + +import com.finfellows.domain.post.domain.Content; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ContentRepository extends JpaRepository { +} diff --git a/src/main/java/com/finfellows/domain/post/dto/request/ContentRequest.java b/src/main/java/com/finfellows/domain/post/dto/request/ContentRequest.java new file mode 100644 index 0000000..877e00b --- /dev/null +++ b/src/main/java/com/finfellows/domain/post/dto/request/ContentRequest.java @@ -0,0 +1,18 @@ +package com.finfellows.domain.post.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ContentRequest { + private Long id; + private LocalDateTime created_at; + private String title; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/post/dto/response/ContentResponse.java b/src/main/java/com/finfellows/domain/post/dto/response/ContentResponse.java new file mode 100644 index 0000000..8aacfbd --- /dev/null +++ b/src/main/java/com/finfellows/domain/post/dto/response/ContentResponse.java @@ -0,0 +1,18 @@ +package com.finfellows.domain.post.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ContentResponse { + private Long id; + private LocalDateTime created_at; + private String title; + private String content; +} diff --git a/src/main/java/com/finfellows/domain/post/presentation/ContentController.java b/src/main/java/com/finfellows/domain/post/presentation/ContentController.java new file mode 100644 index 0000000..9ebf354 --- /dev/null +++ b/src/main/java/com/finfellows/domain/post/presentation/ContentController.java @@ -0,0 +1,80 @@ +package com.finfellows.domain.post.presentation; + +import com.finfellows.domain.educontent.dto.response.EduContentResponse; +import com.finfellows.domain.post.application.ContentService; +import com.finfellows.domain.post.dto.request.ContentRequest; +import com.finfellows.domain.post.dto.response.ContentResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/post") +@Tag(name = "Content", description = "Content API") +public class ContentController { + private final ContentService contentService; + + @Operation(summary = "공지사항 작성", description = "공지사항을 작성합니다.") + @ApiResponse(responseCode = "201", description = "공지사항을 작성 성공", content={ + @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = ContentResponse.class))) + }) + @PostMapping("") + public ResponseEntity saveContent(@RequestBody ContentResponse request) { + com.finfellows.domain.post.domain.Content response = contentService.createContent(request); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + @Operation(summary = "공지사항 목록 조회", description = "공지사항 목록을 조회합니다.") + @ApiResponse(responseCode = "200", description = "공지사항 목록 조회 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ContentResponse.class)) + }) + @GetMapping("") + public ResponseEntity> getAllContents() { + List responseList = contentService.getAllContents(); + return new ResponseEntity<>(responseList, HttpStatus.OK); + } + + + @Operation(summary = "공지사항 조회", description = "공지사항을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "공지사항 조회 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ContentResponse.class)) + }), + @ApiResponse(responseCode = "404", description = "공지사항을 찾을 수 없습니다.") + }) + + @GetMapping("/{id}") + public ResponseEntity getContent(@PathVariable Long id) { + ContentResponse response = contentService.getContent(id); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @Operation(summary = "공지사항 삭제", description = "공지사항을 삭제합니다.") + @ApiResponse(responseCode = "204", description = "공지사항 삭제 성공") + @DeleteMapping("/{id}") + public ResponseEntity deleteContent(@PathVariable Long id) { + contentService.deleteContent(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Operation(summary = "공지사항 수정", description = "공지사항을 수정합니다.") + @ApiResponse(responseCode = "200", description = "공지사항 수정 성공", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ContentResponse.class)) + }) + @PatchMapping("/{id}") + public ResponseEntity updateContent(@PathVariable Long id, @RequestBody ContentRequest request) { + ContentResponse updatedContent = contentService.updateContent(id, request); + return new ResponseEntity<>(updatedContent, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java b/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java index b6659ff..82ffb98 100644 --- a/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java +++ b/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java @@ -1,16 +1,21 @@ package com.finfellows.domain.product.application; -import com.finfellows.domain.product.dto.condition.DepositSearchCondition; -import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.domain.product.dto.condition.FinancialProductSearchCondition; +import com.finfellows.domain.product.dto.response.DepositDetailRes; +import com.finfellows.domain.product.dto.response.SavingDetailRes; +import com.finfellows.domain.product.dto.response.SearchFinancialProductRes; import com.finfellows.global.config.security.token.UserPrincipal; import com.finfellows.global.payload.PagedResponse; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import java.util.List; public interface FinancialProductService { - PagedResponse findDepositProducts(UserPrincipal userPrincipal, DepositSearchCondition depositSearchCondition, Pageable pageable); + PagedResponse findDepositProducts(UserPrincipal userPrincipal, FinancialProductSearchCondition financialProductSearchCondition, Pageable pageable); + PagedResponse findSavingProducts(UserPrincipal userPrincipal, FinancialProductSearchCondition financialProductSearchCondition, Pageable pageable); + DepositDetailRes getDepositDetail(UserPrincipal userPrincipal, Long depositId); + SavingDetailRes getSavingDetail(UserPrincipal userPrincipal, Long savingId); + List findBanks(String bankType); } diff --git a/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java b/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java index 1d3163d..a414114 100644 --- a/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java +++ b/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java @@ -1,8 +1,16 @@ package com.finfellows.domain.product.application; +import com.finfellows.domain.product.domain.FinancialProduct; +import com.finfellows.domain.product.domain.FinancialProductOption; +import com.finfellows.domain.product.domain.FinancialProductType; +import com.finfellows.domain.product.domain.repository.FinancialProductOptionRepository; import com.finfellows.domain.product.domain.repository.FinancialProductRepository; -import com.finfellows.domain.product.dto.condition.DepositSearchCondition; -import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.domain.product.dto.condition.FinancialProductSearchCondition; +import com.finfellows.domain.product.dto.response.DepositDetailRes; +import com.finfellows.domain.product.dto.response.SavingDetailRes; +import com.finfellows.domain.product.dto.response.SearchFinancialProductRes; +import com.finfellows.domain.product.exception.InvalidFinancialProductException; +import com.finfellows.domain.product.exception.ProductTypeMismatchException; import com.finfellows.global.config.security.token.UserPrincipal; import com.finfellows.global.payload.PagedResponse; import lombok.RequiredArgsConstructor; @@ -11,6 +19,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Comparator; import java.util.List; @RequiredArgsConstructor @@ -19,12 +28,69 @@ public class FinancialProductServiceImpl implements FinancialProductService { private final FinancialProductRepository financialProductRepository; + private final FinancialProductOptionRepository financialProductOptionRepository; @Override - public PagedResponse findDepositProducts(UserPrincipal userPrincipal, DepositSearchCondition depositSearchCondition, Pageable pageable) { - Page financialProducts = financialProductRepository.findFinancialProducts(depositSearchCondition, pageable); + public PagedResponse findDepositProducts(UserPrincipal userPrincipal, FinancialProductSearchCondition financialProductSearchCondition, Pageable pageable) { + Page financialProducts = financialProductRepository.findFinancialProducts(financialProductSearchCondition, pageable, FinancialProductType.DEPOSIT); return new PagedResponse<>(financialProducts); } -} + @Override + public PagedResponse findSavingProducts(UserPrincipal userPrincipal, FinancialProductSearchCondition financialProductSearchCondition, Pageable pageable) { + Page financialProducts = financialProductRepository.findFinancialProducts(financialProductSearchCondition, pageable, FinancialProductType.SAVING); + + return new PagedResponse<>(financialProducts); + } + + @Override + public DepositDetailRes getDepositDetail(UserPrincipal userPrincipal, Long depositId) { + FinancialProduct deposit = financialProductRepository.findById(depositId) + .orElseThrow(InvalidFinancialProductException::new); + + if(!deposit.getFinancialProductType().equals(FinancialProductType.DEPOSIT)) + throw new ProductTypeMismatchException(); + + List depositOptions = financialProductOptionRepository.findFinancialProductOptionsByFinancialProduct(deposit); + List terms = depositOptions.stream() + .map(FinancialProductOption::getSavingsTerm) + .distinct() + .sorted() + .toList(); + + FinancialProductOption maxOption = depositOptions.stream() + .max(Comparator.comparing(FinancialProductOption::getMaximumPreferredInterestRate)) + .orElse(null); + + return DepositDetailRes.toDto(deposit, maxOption, terms); + } + + @Override + public SavingDetailRes getSavingDetail(UserPrincipal userPrincipal, Long savingId) { + FinancialProduct saving = financialProductRepository.findById(savingId) + .orElseThrow(InvalidFinancialProductException::new); + + if(!saving.getFinancialProductType().equals(FinancialProductType.SAVING)) + throw new ProductTypeMismatchException(); + + List savingOptions = financialProductOptionRepository.findFinancialProductOptionsByFinancialProduct(saving); + List terms = savingOptions.stream() + .map(FinancialProductOption::getSavingsTerm) + .distinct() + .sorted() + .toList(); + + FinancialProductOption maxOption = savingOptions.stream() + .max(Comparator.comparing(FinancialProductOption::getMaximumPreferredInterestRate)) + .orElse(null); + + return SavingDetailRes.toDto(saving, maxOption, terms); + } + + @Override + public List findBanks(String bankGroupNo) { + return financialProductRepository.findBanks(bankGroupNo); + } + +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductOptionRepository.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductOptionRepository.java new file mode 100644 index 0000000..a9f567d --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductOptionRepository.java @@ -0,0 +1,14 @@ +package com.finfellows.domain.product.domain.repository; + +import com.finfellows.domain.product.domain.FinancialProduct; +import com.finfellows.domain.product.domain.FinancialProductOption; +import jakarta.persistence.Entity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface FinancialProductOptionRepository extends JpaRepository { + + List findFinancialProductOptionsByFinancialProduct(FinancialProduct financialProduct); + +} diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java index 24b5d1f..2260c1e 100644 --- a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java @@ -1,8 +1,8 @@ package com.finfellows.domain.product.domain.repository; -import com.finfellows.domain.product.domain.FinancialProduct; -import com.finfellows.domain.product.dto.condition.DepositSearchCondition; -import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.domain.product.domain.FinancialProductType; +import com.finfellows.domain.product.dto.condition.FinancialProductSearchCondition; +import com.finfellows.domain.product.dto.response.SearchFinancialProductRes; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -10,6 +10,7 @@ public interface FinancialProductQueryDslRepository { - Page findFinancialProducts(DepositSearchCondition depositSearchCondition, Pageable pageable); + Page findFinancialProducts(FinancialProductSearchCondition financialProductSearchCondition, Pageable pageable, FinancialProductType financialProductType); + List findBanks(String bankGroupNo); } diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java index 4496eb2..1ab49e5 100644 --- a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java @@ -1,24 +1,20 @@ package com.finfellows.domain.product.domain.repository; -import com.finfellows.domain.product.domain.FinancialProduct; import com.finfellows.domain.product.domain.FinancialProductType; -import com.finfellows.domain.product.domain.QFinancialProduct; -import com.finfellows.domain.product.domain.QFinancialProductOption; -import com.finfellows.domain.product.dto.condition.DepositSearchCondition; -import com.finfellows.domain.product.dto.response.QSearchDepositRes; -import com.finfellows.domain.product.dto.response.SearchDepositRes; -import com.querydsl.core.QueryResults; +import com.finfellows.domain.product.dto.condition.FinancialProductSearchCondition; +import com.finfellows.domain.product.dto.response.QSearchFinancialProductRes; +import com.finfellows.domain.product.dto.response.SearchFinancialProductRes; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Objects; import static com.finfellows.domain.product.domain.QFinancialProduct.*; import static com.finfellows.domain.product.domain.QFinancialProductOption.*; @@ -30,9 +26,9 @@ public class FinancialProductQueryDslRepositoryImpl implements FinancialProductQ private final JPAQueryFactory queryFactory; @Override - public Page findFinancialProducts(DepositSearchCondition depositSearchCondition, Pageable pageable) { - List results = queryFactory - .select(new QSearchDepositRes( + public Page findFinancialProducts(FinancialProductSearchCondition financialProductSearchCondition, Pageable pageable, FinancialProductType financialProductType) { + List results = queryFactory + .select(new QSearchFinancialProductRes( financialProduct.id, financialProduct.productName, financialProduct.companyName, @@ -42,10 +38,10 @@ public Page findFinancialProducts(DepositSearchCondition depos .from(financialProductOption) .leftJoin(financialProductOption.financialProduct, financialProduct) .where( - financialProduct.financialProductType.eq(FinancialProductType.DEPOSIT), - typeEq(depositSearchCondition.getType()), - preferentialConditionEq(depositSearchCondition.getPreferentialCondition()), - termEq(depositSearchCondition.getTerm()) + financialProduct.financialProductType.eq(financialProductType), + typeEq(financialProductSearchCondition.getType()), + bankGroupNoEq(financialProductSearchCondition.getBankGroupNo()), + termEq(financialProductSearchCondition.getTerm()) ) .orderBy(financialProductOption.maximumPreferredInterestRate.desc()) .offset(pageable.getOffset()) @@ -57,16 +53,31 @@ public Page findFinancialProducts(DepositSearchCondition depos .from(financialProductOption) .leftJoin(financialProductOption.financialProduct, financialProduct) .where( - financialProduct.financialProductType.eq(FinancialProductType.DEPOSIT), - typeEq(depositSearchCondition.getType()), - preferentialConditionEq(depositSearchCondition.getPreferentialCondition()), - termEq(depositSearchCondition.getTerm()) + financialProduct.financialProductType.eq(financialProductType), + typeEq(financialProductSearchCondition.getType()), + bankGroupNoEq(financialProductSearchCondition.getBankGroupNo()), + termEq(financialProductSearchCondition.getTerm()) ); // Page 객체 생성 return PageableExecutionUtils.getPage(results, pageable, countQuery::fetchOne); } + @Override + public List findBanks(String bankGroupNo) { + return queryFactory + .select(financialProduct.companyName) + .from(financialProduct) + .where(bankGroupNoEq(bankGroupNo)) + .distinct() + .orderBy(financialProduct.companyName.asc()) + .fetch(); + } + + private BooleanExpression bankGroupNoEq(String bankType) { + return financialProduct.topFinancialGroupNo.eq(Objects.requireNonNullElse(bankType, "020000")); + } + private BooleanExpression termEq(Integer term) { return term != null ? financialProductOption.savingsTerm.eq(term) : null; } @@ -74,9 +85,5 @@ private BooleanExpression termEq(Integer term) { private BooleanExpression typeEq(String type) { return type != null ? financialProductOption.financialProduct.joinWay.contains(type) : null; } - - private BooleanExpression preferentialConditionEq(String preferentialCondition) { - return preferentialCondition != null ? financialProductOption.financialProduct.specialCondition.contains(preferentialCondition) : null; - } - + } diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java index 62eb298..d710dea 100644 --- a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java @@ -7,4 +7,4 @@ public interface FinancialProductRepository extends JpaRepository, FinancialProductQueryDslRepository { -} +} \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/product/dto/condition/FinancialProductSearchCondition.java b/src/main/java/com/finfellows/domain/product/dto/condition/FinancialProductSearchCondition.java new file mode 100644 index 0000000..6f1bebd --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/condition/FinancialProductSearchCondition.java @@ -0,0 +1,18 @@ +package com.finfellows.domain.product.dto.condition; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class FinancialProductSearchCondition { + + @Schema(type = "string", example = "020000(1금융권), 030300(저축은행)", description = "은행 종류입니다.") + private String bankGroupNo; + + @Schema(type = "int", example = "6", description = "금융 상품 기간입니다.") + private Integer term; + + @Schema(type = "string", example = "누구나 가입", description = "금융 상품 상품 유형입니다.") + private String type; + +} diff --git a/src/main/java/com/finfellows/domain/product/dto/response/DepositDetailRes.java b/src/main/java/com/finfellows/domain/product/dto/response/DepositDetailRes.java new file mode 100644 index 0000000..b0d5d48 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/response/DepositDetailRes.java @@ -0,0 +1,38 @@ +package com.finfellows.domain.product.dto.response; + +import com.finfellows.domain.product.domain.FinancialProduct; +import com.finfellows.domain.product.domain.FinancialProductOption; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +@Builder +public class DepositDetailRes { + + private String productName; + private String bankName; + private String maxInterestRate; + private String interestRate; + private List savingTerms; + private Integer maxLimit; + private String joinMember; + private String etcNote; + + public static DepositDetailRes toDto(FinancialProduct deposit, FinancialProductOption depositOption, List terms) { + return DepositDetailRes.builder() + .productName(deposit.getProductName()) + .bankName(deposit.getCompanyName()) + .maxInterestRate(depositOption.getMaximumPreferredInterestRate()) + .interestRate(depositOption.getInterestRate()) + .savingTerms(terms) + .maxLimit(deposit.getMaxLimit()) + .joinMember(deposit.getJoinMember()) + .etcNote(deposit.getEtcNote()) + .build(); + } + +} diff --git a/src/main/java/com/finfellows/domain/product/dto/response/SavingDetailRes.java b/src/main/java/com/finfellows/domain/product/dto/response/SavingDetailRes.java new file mode 100644 index 0000000..43186c5 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/response/SavingDetailRes.java @@ -0,0 +1,38 @@ +package com.finfellows.domain.product.dto.response; + +import com.finfellows.domain.product.domain.FinancialProduct; +import com.finfellows.domain.product.domain.FinancialProductOption; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +@Builder +public class SavingDetailRes { + + private String productName; + private String bankName; + private String maxInterestRate; + private String interestRate; + private List savingTerms; + private Integer maxLimit; + private String joinMember; + private String etcNote; + + public static SavingDetailRes toDto(FinancialProduct deposit, FinancialProductOption depositOption, List terms) { + return SavingDetailRes.builder() + .productName(deposit.getProductName()) + .bankName(deposit.getCompanyName()) + .maxInterestRate(depositOption.getMaximumPreferredInterestRate()) + .interestRate(depositOption.getInterestRate()) + .savingTerms(terms) + .maxLimit(deposit.getMaxLimit()) + .joinMember(deposit.getJoinMember()) + .etcNote(deposit.getEtcNote()) + .build(); + } + +} diff --git a/src/main/java/com/finfellows/domain/product/dto/response/SearchFinancialProductRes.java b/src/main/java/com/finfellows/domain/product/dto/response/SearchFinancialProductRes.java new file mode 100644 index 0000000..f4df387 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/response/SearchFinancialProductRes.java @@ -0,0 +1,24 @@ +package com.finfellows.domain.product.dto.response; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Data; + +@Data +public class SearchFinancialProductRes { + + private Long id; + private String productName; + private String bankName; + private String maxInterestRate; + private String interestRate; + + @QueryProjection + public SearchFinancialProductRes(Long id, String productName, String bankName, String maxInterestRate, String interestRate) { + this.id = id; + this.productName = productName; + this.bankName = bankName; + this.maxInterestRate = maxInterestRate; + this.interestRate = interestRate; + } + +} diff --git a/src/main/java/com/finfellows/domain/product/exception/InvalidFinancialProductException.java b/src/main/java/com/finfellows/domain/product/exception/InvalidFinancialProductException.java new file mode 100644 index 0000000..26b93d7 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/exception/InvalidFinancialProductException.java @@ -0,0 +1,8 @@ +package com.finfellows.domain.product.exception; + +public class InvalidFinancialProductException extends RuntimeException { + + public InvalidFinancialProductException() { + super("유효하지 않은 금융상품입니다."); + } +} diff --git a/src/main/java/com/finfellows/domain/product/exception/ProductTypeMismatchException.java b/src/main/java/com/finfellows/domain/product/exception/ProductTypeMismatchException.java new file mode 100644 index 0000000..ede9ac6 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/exception/ProductTypeMismatchException.java @@ -0,0 +1,9 @@ +package com.finfellows.domain.product.exception; + +public class ProductTypeMismatchException extends RuntimeException { + + public ProductTypeMismatchException() { + super("조회하려는 상품의 Type이 일치하지 않습니다."); + } + +} diff --git a/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java b/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java index 493ac70..73ad5ff 100644 --- a/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java +++ b/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java @@ -1,8 +1,10 @@ package com.finfellows.domain.product.presentation; import com.finfellows.domain.product.application.FinancialProductServiceImpl; -import com.finfellows.domain.product.dto.condition.DepositSearchCondition; -import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.domain.product.dto.condition.FinancialProductSearchCondition; +import com.finfellows.domain.product.dto.response.DepositDetailRes; +import com.finfellows.domain.product.dto.response.SavingDetailRes; +import com.finfellows.domain.product.dto.response.SearchFinancialProductRes; import com.finfellows.global.config.security.token.CurrentUser; import com.finfellows.global.config.security.token.UserPrincipal; import com.finfellows.global.payload.ErrorResponse; @@ -30,18 +32,70 @@ public class FinancialProductController { private final FinancialProductServiceImpl financialProductServiceImpl; + @Operation(summary = "은행 리스트 조회", description = "은행 리스트를 조건에 따라 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "은행 리스트 조회 성공", content = {@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = String.class)))}), + @ApiResponse(responseCode = "400", description = "은행 리스트 조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/bank") + public ResponseCustom> findBanks( + @Parameter(description = "1금융권(020000), 저축은행(030300)", required = true) @RequestParam String bankGroupNo + ) { + return ResponseCustom.OK(financialProductServiceImpl.findBanks(bankGroupNo)); + } + @Operation(summary = "예금 정보 조회", description = "예금 정보를 조건에 따라 조회합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "예금 정보 조회 성공", content = {@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = SearchDepositRes.class)))}), + @ApiResponse(responseCode = "200", description = "예금 정보 조회 성공", content = {@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = SearchFinancialProductRes.class)))}), @ApiResponse(responseCode = "400", description = "예금 정보 조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), }) @GetMapping("/deposit") - public ResponseCustom> findDepositProducts( + public ResponseCustom> findDepositProducts( + @Parameter(description = "AccessToken 을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, + @ModelAttribute FinancialProductSearchCondition financialProductSearchCondition, + @Parameter(description = "조회 할 페이지와 페이지 크기를 입력해주세요") @RequestParam Pageable pageable + ) { + return ResponseCustom.OK(financialProductServiceImpl.findDepositProducts(userPrincipal, financialProductSearchCondition, pageable)); + } + + @Operation(summary = "적금 정보 조회", description = "적금 정보를 조건에 따라 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "적금 정보 조회 성공", content = {@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = SearchFinancialProductRes.class)))}), + @ApiResponse(responseCode = "400", description = "적금 정보 조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/saving") + public ResponseCustom> findSavingProducts( + @Parameter(description = "AccessToken 을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, + @ModelAttribute FinancialProductSearchCondition financialProductSearchCondition, + @Parameter(description = "조회 할 페이지와 페이지 크기를 입력해주세요") @RequestParam Pageable pageable + ) { + return ResponseCustom.OK(financialProductServiceImpl.findSavingProducts(userPrincipal, financialProductSearchCondition, pageable)); + } + + @Operation(summary = "예금 상세 정보 조회", description = "예금 상세 정보를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "예금 상세 정보 조회 성공", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = DepositDetailRes.class))}), + @ApiResponse(responseCode = "400", description = "예금 상세 정보 조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/deposit/{deposit-id}") + public ResponseCustom getDepositDetail( + @Parameter(description = "AccessToken 을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, + @PathVariable(name = "deposit-id") Long depositId + ) { + return ResponseCustom.OK(financialProductServiceImpl.getDepositDetail(userPrincipal, depositId)); + } + + @Operation(summary = "적금 상세 정보 조회", description = "적금 상세 정보를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "적금 상세 정보 조회 성공", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = SavingDetailRes.class))}), + @ApiResponse(responseCode = "400", description = "적금 상세 정보 조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/saving/{saving-id}") + public ResponseCustom getSavingDetail( @Parameter(description = "AccessToken 을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, - @ModelAttribute DepositSearchCondition depositSearchCondition, - Pageable pageable + @PathVariable(name = "saving-id") Long savingId ) { - return ResponseCustom.OK(financialProductServiceImpl.findDepositProducts(userPrincipal, depositSearchCondition, pageable)); + return ResponseCustom.OK(financialProductServiceImpl.getSavingDetail(userPrincipal, savingId)); } } diff --git a/src/main/java/com/finfellows/global/config/security/SecurityConfig.java b/src/main/java/com/finfellows/global/config/security/SecurityConfig.java index 0b8cfdb..a4cce10 100644 --- a/src/main/java/com/finfellows/global/config/security/SecurityConfig.java +++ b/src/main/java/com/finfellows/global/config/security/SecurityConfig.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; @@ -20,8 +21,6 @@ import static org.springframework.security.config.Customizer.*; - - @RequiredArgsConstructor @Configuration @EnableWebSecurity @@ -58,13 +57,22 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .permitAll() .requestMatchers("/swagger", "/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**") .permitAll() - .requestMatchers("/login/**", "/auth/**", "/oauth2/**") + .requestMatchers("/login/**", "/auth/**", "/oauth2/**", "api/**") .permitAll() .requestMatchers("/blog/**", "/financial-products/**") .permitAll() + + .requestMatchers(HttpMethod.POST, "https://api.openai.com/v1/completions") + .permitAll() + + .requestMatchers("/api/v1/chat-gpt") + .permitAll() + .anyRequest() .authenticated()); + + http.addFilterBefore(customOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/finfellows/global/payload/PagedResponse.java b/src/main/java/com/finfellows/global/payload/PagedResponse.java index 33ee083..d78b289 100644 --- a/src/main/java/com/finfellows/global/payload/PagedResponse.java +++ b/src/main/java/com/finfellows/global/payload/PagedResponse.java @@ -9,12 +9,12 @@ public class PagedResponse { private int pageNumber; - private int totalPages; + private int maxPage; private List content; public PagedResponse(Page page) { this.pageNumber = page.getNumber(); - this.totalPages = page.getTotalPages(); + this.maxPage = page.getTotalPages() - 1; this.content = page.getContent(); }