Skip to content

Commit f8c0165

Browse files
authored
Merge pull request #19 from 9oormthon-univ/feat/questions-trash-type
[Feat] 챗봇 단어로 쓰레기 배출 방법 검색 구현
2 parents 499d5bb + 33b69fd commit f8c0165

File tree

15 files changed

+277
-55
lines changed

15 files changed

+277
-55
lines changed

src/main/java/com/trashheroesbe/feature/question/api/QuestionController.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
import com.trashheroesbe.feature.trash.dto.response.TrashItemResponse;
88
import com.trashheroesbe.feature.trash.dto.response.TrashTypeResponse;
99
import com.trashheroesbe.global.response.ApiResponse;
10+
import jakarta.validation.constraints.NotBlank;
1011
import java.util.List;
1112
import lombok.RequiredArgsConstructor;
1213
import org.springframework.web.bind.annotation.GetMapping;
1314
import org.springframework.web.bind.annotation.PathVariable;
1415
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RequestParam;
1517
import org.springframework.web.bind.annotation.RestController;
1618

1719
@RestController
@@ -38,11 +40,23 @@ public ApiResponse<List<TrashItemResponse>> getTrashItems(
3840
}
3941

4042
@Override
41-
@GetMapping("/trash-items/{trashItemId}")
42-
public ApiResponse<List<TrashDescriptionResponse>> getTrashDescriptions(
43-
@PathVariable Long trashItemId
43+
@GetMapping("/trash-types/{trashTypeId}/descriptions")
44+
public ApiResponse<TrashDescriptionResponse> getTrashDescriptions(
45+
@PathVariable Long trashTypeId
4446
) {
4547
// TODO : 여기서 question객체 생성해서 검색기록 추적할 수 있도록 하면 좋을 거 같음
46-
return ApiResponse.success(OK, null);
48+
TrashDescriptionResponse response = questionService.getTrashDescriptions(trashTypeId);
49+
return ApiResponse.success(OK, response);
4750
}
51+
52+
@Override
53+
@GetMapping("/search")
54+
public ApiResponse<TrashDescriptionResponse> searchTrashDescription(
55+
@RequestParam("keyword") String keyword
56+
) {
57+
TrashDescriptionResponse response = questionService.searchTrashDescription(keyword);
58+
return ApiResponse.success(OK, response);
59+
}
60+
61+
4862
}

src/main/java/com/trashheroesbe/feature/question/api/QuestionControllerApi.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.trashheroesbe.global.response.ApiResponse;
77
import io.swagger.v3.oas.annotations.Operation;
88
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import jakarta.validation.constraints.NotBlank;
910
import java.util.List;
1011

1112
@Tag(name = "Question", description = "질문 관련 API(챗봇)")
@@ -17,6 +18,9 @@ public interface QuestionControllerApi {
1718
@Operation(summary = "쓰레기 카테고리별 품목 조회(쓰레기 품목 조회)", description = "trashTypeId를 통해 쓰레기 품목을 조회 합니다.")
1819
ApiResponse<List<TrashItemResponse>> getTrashItems(Long trashTypeId);
1920

20-
@Operation(summary = "쓰레기 배출 방법 조회하기", description = "trashItemId를 통해 쓰레기 배출 방법을 조회 합니다.")
21-
ApiResponse<List<TrashDescriptionResponse>> getTrashDescriptions(Long trashItemId);
21+
@Operation(summary = "쓰레기 배출 방법 조회하기", description = "trashTypeId를 통해 쓰레기 배출 방법을 조회 합니다.")
22+
ApiResponse<TrashDescriptionResponse> getTrashDescriptions(Long trashTypeId);
23+
24+
@Operation(summary = "단어로 쓰레기 배출 방법 조회하기", description = "keyword로 쓰레기 배출방법을 조회한다.")
25+
ApiResponse<TrashDescriptionResponse> searchTrashDescription(@NotBlank String keyword);
2226
}
Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package com.trashheroesbe.feature.question.application;
22

3+
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_EXISTS_TRASH_DESCRIPTION;
34
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_EXISTS_TRASH_ITEM;
45
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_EXISTS_TRASH_TYPE;
56

7+
import com.trashheroesbe.feature.trash.domain.Type;
8+
import com.trashheroesbe.feature.trash.domain.entity.TrashDescription;
69
import com.trashheroesbe.feature.trash.domain.entity.TrashItem;
710
import com.trashheroesbe.feature.trash.domain.entity.TrashType;
11+
import com.trashheroesbe.feature.trash.domain.service.TrashDescriptionFinder;
812
import com.trashheroesbe.feature.trash.domain.service.TrashItemFinder;
913
import com.trashheroesbe.feature.trash.domain.service.TrashTypeFinder;
14+
import com.trashheroesbe.feature.trash.dto.response.TrashDescriptionResponse;
1015
import com.trashheroesbe.feature.trash.dto.response.TrashItemResponse;
1116
import com.trashheroesbe.feature.trash.dto.response.TrashTypeResponse;
1217
import com.trashheroesbe.global.exception.BusinessException;
18+
import com.trashheroesbe.infrastructure.port.gpt.ChatAIClientPort;
1319
import java.util.List;
1420
import java.util.stream.Collectors;
1521
import lombok.RequiredArgsConstructor;
@@ -23,24 +29,40 @@ public class QuestionService {
2329

2430
private final TrashTypeFinder trashTypeFinder;
2531
private final TrashItemFinder trashItemFinder;
32+
private final TrashDescriptionFinder trashDescriptionFinder;
33+
private final ChatAIClientPort chatAIClientPort;
2634

2735
public List<TrashTypeResponse> getTrashTypes() {
2836
List<TrashType> trashTypes = trashTypeFinder.getAllTrashTypes();
29-
if (trashTypes.isEmpty()) {
30-
throw new BusinessException(NOT_EXISTS_TRASH_TYPE);
31-
}
37+
3238
return trashTypes.stream()
39+
.filter(trashType -> trashType.getType() != Type.UNKNOWN)
3340
.map(TrashTypeResponse::from)
3441
.collect(Collectors.toList());
3542
}
3643

3744
public List<TrashItemResponse> getTrashItems(Long trashTypeId) {
3845
List<TrashItem> trashItems = trashItemFinder.findTrashItemsByTrashTypeId(trashTypeId);
39-
if (trashItems.isEmpty()) {
40-
throw new BusinessException(NOT_EXISTS_TRASH_ITEM);
41-
}
4246
return trashItems.stream()
4347
.map(TrashItemResponse::from)
4448
.collect(Collectors.toList());
4549
}
50+
51+
public TrashDescriptionResponse getTrashDescriptions(Long trashTypeId) {
52+
TrashDescription trashDescription = trashDescriptionFinder.findTrashDescriptionsByTrashTypeId(
53+
trashTypeId);
54+
return TrashDescriptionResponse.from(trashDescription);
55+
}
56+
57+
public TrashDescriptionResponse searchTrashDescription(String keyword) {
58+
Type type = chatAIClientPort.findSimilarTrashItem(keyword);
59+
if (type == null) {
60+
return TrashDescriptionResponse.ofNotFound();
61+
}
62+
63+
TrashType trashType = trashTypeFinder.getTrashType(type);
64+
TrashDescription trashDescription = trashDescriptionFinder.findTrashDescriptionsByTrashTypeId(
65+
trashType.getId());
66+
return TrashDescriptionResponse.from(trashDescription);
67+
}
4668
}

src/main/java/com/trashheroesbe/feature/trash/domain/entity/TrashDescription.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.trashheroesbe.feature.trash.domain.entity;
22

33
import jakarta.persistence.*;
4+
import java.util.Arrays;
5+
import java.util.List;
46
import lombok.*;
57

68
@Getter
@@ -29,11 +31,13 @@ public class TrashDescription {
2931
private String cautionNote;
3032

3133
// 줄바꿈으로 STEP 리스트 변환
32-
public java.util.List<String> steps() {
33-
if (methodDetail == null || methodDetail.isBlank()) return java.util.List.of();
34-
return java.util.Arrays.stream(methodDetail.split("\\r?\\n"))
35-
.map(String::trim)
36-
.filter(s -> !s.isBlank())
37-
.toList();
34+
public List<String> steps() {
35+
if (methodDetail == null || methodDetail.isBlank()) {
36+
return List.of();
37+
}
38+
return Arrays.stream(methodDetail.split("\\r?\\n"))
39+
.map(String::trim)
40+
.filter(s -> !s.isBlank())
41+
.toList();
3842
}
3943
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.trashheroesbe.feature.trash.domain.service;
2+
3+
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_EXISTS_TRASH_DESCRIPTION;
4+
5+
import com.trashheroesbe.feature.trash.domain.entity.TrashDescription;
6+
import com.trashheroesbe.feature.trash.infrastructure.TrashDescriptionRepository;
7+
import com.trashheroesbe.global.exception.BusinessException;
8+
import java.util.List;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.stereotype.Service;
11+
import org.springframework.transaction.annotation.Transactional;
12+
13+
@Service
14+
@Transactional(readOnly = true)
15+
@RequiredArgsConstructor
16+
public class TrashDescriptionFinder {
17+
18+
private final TrashDescriptionRepository trashDescriptionRepository;
19+
20+
public TrashDescription findTrashDescriptionsByTrashTypeId(Long trashTypeId) {
21+
return trashDescriptionRepository.findByTrashTypeId(trashTypeId)
22+
.orElseThrow(() -> new BusinessException(NOT_EXISTS_TRASH_DESCRIPTION));
23+
}
24+
}

src/main/java/com/trashheroesbe/feature/trash/domain/service/TrashItemFinder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.trashheroesbe.feature.trash.domain.service;
22

3+
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_EXISTS_TRASH_ITEM;
4+
35
import com.trashheroesbe.feature.trash.domain.entity.TrashItem;
46
import com.trashheroesbe.feature.trash.infrastructure.TrashItemRepository;
7+
import com.trashheroesbe.global.exception.BusinessException;
58
import java.util.List;
69
import lombok.RequiredArgsConstructor;
710
import org.springframework.stereotype.Service;
@@ -15,6 +18,10 @@ public class TrashItemFinder {
1518
private final TrashItemRepository trashItemRepository;
1619

1720
public List<TrashItem> findTrashItemsByTrashTypeId(Long trashTypeId) {
18-
return trashItemRepository.findByTrashTypeId(trashTypeId);
21+
List<TrashItem> trashItems = trashItemRepository.findByTrashTypeId(trashTypeId);
22+
if (trashItems.isEmpty()) {
23+
throw new BusinessException(NOT_EXISTS_TRASH_ITEM);
24+
}
25+
return trashItems;
1926
}
2027
}

src/main/java/com/trashheroesbe/feature/trash/domain/service/TrashTypeFinder.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package com.trashheroesbe.feature.trash.domain.service;
22

3+
import static com.trashheroesbe.global.response.type.ErrorCode.ENTITY_NOT_FOUND;
4+
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_EXISTS_TRASH_TYPE;
5+
6+
import com.trashheroesbe.feature.trash.domain.Type;
37
import com.trashheroesbe.feature.trash.domain.entity.TrashType;
48
import com.trashheroesbe.feature.trash.infrastructure.TrashTypeRepository;
9+
import com.trashheroesbe.global.exception.BusinessException;
510
import java.util.List;
611
import lombok.RequiredArgsConstructor;
712
import org.springframework.stereotype.Service;
@@ -15,6 +20,15 @@ public class TrashTypeFinder {
1520
private final TrashTypeRepository trashTypeRepository;
1621

1722
public List<TrashType> getAllTrashTypes() {
18-
return trashTypeRepository.findAll();
23+
List<TrashType> trashTypes = trashTypeRepository.findAllByOrderByIdAsc();
24+
if (trashTypes.isEmpty()) {
25+
throw new BusinessException(NOT_EXISTS_TRASH_TYPE);
26+
}
27+
return trashTypes;
28+
}
29+
30+
public TrashType getTrashType(Type type) {
31+
return trashTypeRepository.findByType(type)
32+
.orElseThrow(() -> new BusinessException(ENTITY_NOT_FOUND));
1933
}
2034
}
Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,35 @@
11
package com.trashheroesbe.feature.trash.dto.response;
22

3-
public record TrashDescriptionResponse(
3+
import com.trashheroesbe.feature.trash.domain.Type;
4+
import com.trashheroesbe.feature.trash.domain.entity.TrashDescription;
5+
import com.trashheroesbe.feature.trash.domain.entity.TrashItem;
6+
import java.util.List;
7+
import lombok.Builder;
48

9+
@Builder
10+
public record TrashDescriptionResponse(
11+
Long trashDescriptionId,
12+
List<String> guideSteps,
13+
String cautionNote,
14+
String typeName
515
) {
616

17+
public static TrashDescriptionResponse from(TrashDescription trashDescription) {
18+
List<String> steps = trashDescription.steps();
19+
return TrashDescriptionResponse.builder()
20+
.trashDescriptionId(trashDescription.getId())
21+
.guideSteps(steps)
22+
.cautionNote(trashDescription.getCautionNote())
23+
.typeName(trashDescription.getTrashType().getType().getNameKo())
24+
.build();
25+
}
26+
27+
public static TrashDescriptionResponse ofNotFound() {
28+
return TrashDescriptionResponse.builder()
29+
.trashDescriptionId(-1L)
30+
.guideSteps(List.of())
31+
.cautionNote("해당하는 키워드에 분리배출 방법을 찾지 못했습니다.")
32+
.typeName(Type.UNKNOWN.getNameKo())
33+
.build();
34+
}
735
}

src/main/java/com/trashheroesbe/feature/trash/infrastructure/TrashDescriptionRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
import com.trashheroesbe.feature.trash.domain.entity.TrashDescription;
44
import com.trashheroesbe.feature.trash.domain.entity.TrashType;
5+
import java.util.List;
6+
import org.springframework.data.jpa.repository.EntityGraph;
57
import org.springframework.data.jpa.repository.JpaRepository;
68

79
import java.util.Optional;
810

911
public interface TrashDescriptionRepository extends JpaRepository<TrashDescription, Long> {
12+
1013
Optional<TrashDescription> findByTrashType(TrashType trashType);
14+
15+
@EntityGraph(attributePaths = "trashType")
16+
Optional<TrashDescription> findByTrashTypeId(Long trashTypeId);
1117
}

src/main/java/com/trashheroesbe/feature/trash/infrastructure/TrashItemRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import com.trashheroesbe.feature.trash.domain.entity.TrashType;
55
import java.util.List;
66
import java.util.Optional;
7+
import org.springframework.data.jpa.repository.EntityGraph;
78
import org.springframework.data.jpa.repository.JpaRepository;
89
import org.springframework.stereotype.Repository;
910

1011
@Repository
1112
public interface TrashItemRepository extends JpaRepository<TrashItem, Long> {
13+
14+
@EntityGraph(attributePaths = "trashType")
1215
List<TrashItem> findByTrashTypeId(Long trashTypeId);
16+
1317
Optional<TrashItem> findByTrashTypeAndName(TrashType trashType, String name);
1418
}

0 commit comments

Comments
 (0)