Skip to content

Commit 3106476

Browse files
authored
Epic/inquiry (#115)
* feat: 문의하기 기능 추가 * chore: valid 체크 entity -> request로 변경 * chore: 중복코드 수정(memberReadService 재활용) * fix: entity를 response 구조로 변경 및 테스트 * feat: 문의하기 답변 추가 * feat: 내 문의하기 정렬기능 추가 * fix: DTO 어노테이션 변경 * feat: 문의하기 검색 기능 추가 * feat: 문의하기 기능 추가 * feat: 문의하기 답변 추가 * feat: 검색 테스트코드 추가 * fix: 코드 리뷰 반영
1 parent 999ae31 commit 3106476

File tree

7 files changed

+196
-38
lines changed

7 files changed

+196
-38
lines changed

src/main/java/org/myteam/server/inquiry/controller/InquiryController.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@
44
import jakarta.validation.Valid;
55
import lombok.RequiredArgsConstructor;
66
import lombok.extern.slf4j.Slf4j;
7-
import org.myteam.server.global.page.request.PageInfoRequest;
87
import org.myteam.server.global.page.response.PageCustomResponse;
98
import org.myteam.server.global.web.response.ResponseDto;
109
import org.myteam.server.inquiry.dto.request.InquiryFindRequest;
1110
import org.myteam.server.inquiry.dto.request.InquiryRequest;
11+
import org.myteam.server.inquiry.dto.request.InquirySearchRequest;
1212
import org.myteam.server.inquiry.dto.response.InquiryResponse;
1313
import org.myteam.server.inquiry.service.InquiryReadService;
1414
import org.myteam.server.inquiry.service.InquiryWriteService;
1515
import org.myteam.server.util.ClientUtils;
1616
import org.springframework.http.ResponseEntity;
1717
import org.springframework.web.bind.annotation.*;
1818

19-
import java.util.UUID;
20-
2119
import static org.myteam.server.global.web.response.ResponseStatus.SUCCESS;
2220

2321
@Slf4j
@@ -41,9 +39,12 @@ public ResponseEntity<ResponseDto<String>> createInquiry(@Valid @RequestBody Inq
4139
));
4240
}
4341

42+
/**
43+
* TODO: 차후 API로 사용하지 않고 마이 페이지에서 InquiryReadService를 호출하는 식으로 진행
44+
*/
4445
@GetMapping("/my")
45-
public ResponseEntity<ResponseDto<PageCustomResponse<InquiryResponse>>> getMyInquiries(@ModelAttribute @Valid InquiryFindRequest request) {
46-
PageCustomResponse<InquiryResponse> content = inquiryReadService.getInquiriesByMember(request);
46+
public ResponseEntity<ResponseDto<PageCustomResponse<InquiryResponse>>> getMyInquiries(@ModelAttribute @Valid InquirySearchRequest inquirySearchRequest) {
47+
PageCustomResponse<InquiryResponse> content = inquiryReadService.getInquiriesByMember(inquirySearchRequest);
4748

4849
return ResponseEntity.ok(new ResponseDto<>(
4950
SUCCESS.name(),
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.myteam.server.inquiry.domain;
2+
3+
public enum InquirySearchType {
4+
CONTENT,
5+
ANSWER,
6+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.myteam.server.inquiry.dto.request;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import jakarta.validation.constraints.Size;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
import lombok.Setter;
9+
import org.myteam.server.global.page.request.PageInfoServiceRequest;
10+
import org.myteam.server.inquiry.domain.InquiryOrderType;
11+
import org.myteam.server.inquiry.domain.InquirySearchType;
12+
13+
import java.util.UUID;
14+
15+
@Getter
16+
@Setter
17+
@NoArgsConstructor
18+
public class InquirySearchRequest extends PageInfoServiceRequest {
19+
20+
@NotNull(message = "멤버 id는 필수입니다.")
21+
private UUID memberPublicId;
22+
23+
@NotNull(message = "문의하기 정렬 타입은 필수입니다.")
24+
private InquiryOrderType orderType;
25+
26+
private InquirySearchType searchType;
27+
28+
private String keyword;
29+
30+
@Builder
31+
public InquirySearchRequest(UUID memberPublicId, InquiryOrderType orderType, InquirySearchType searchType, String keyword, int page, int size) {
32+
super(page, size);
33+
this.memberPublicId = memberPublicId;
34+
this.orderType = orderType;
35+
this.searchType = searchType;
36+
this.keyword = keyword;
37+
}
38+
}

src/main/java/org/myteam/server/inquiry/repository/InquiryQueryRepository.java

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.myteam.server.inquiry.repository;
22

3+
import com.querydsl.core.BooleanBuilder;
34
import com.querydsl.core.types.OrderSpecifier;
45
import com.querydsl.core.types.Projections;
6+
import com.querydsl.core.types.dsl.BooleanExpression;
57
import com.querydsl.jpa.impl.JPAQueryFactory;
68
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
710
import org.myteam.server.inquiry.domain.InquiryOrderType;
11+
import org.myteam.server.inquiry.domain.InquirySearchType;
812
import org.myteam.server.inquiry.domain.QInquiry;
913
import org.myteam.server.inquiry.domain.QInquiryAnswer;
1014
import org.myteam.server.inquiry.dto.response.InquiryResponse;
@@ -17,6 +21,10 @@
1721
import java.util.Optional;
1822
import java.util.UUID;
1923

24+
import static org.myteam.server.inquiry.domain.QInquiry.*;
25+
import static org.myteam.server.inquiry.domain.QInquiryAnswer.*;
26+
27+
@Slf4j
2028
@Repository
2129
@RequiredArgsConstructor
2230
public class InquiryQueryRepository {
@@ -25,13 +33,12 @@ public class InquiryQueryRepository {
2533

2634
public Page<InquiryResponse> getInquiryList(UUID memberPublicId,
2735
InquiryOrderType orderType,
36+
InquirySearchType searchType,
37+
String keyword,
2838
Pageable pageable) {
29-
QInquiry inquiry = QInquiry.inquiry;
30-
QInquiryAnswer inquiryAnswer = QInquiryAnswer.inquiryAnswer;
31-
3239
// 정렬 조건 설정
3340
OrderSpecifier<?> orderSpecifier = getOrderSpecifier(orderType, inquiry, inquiryAnswer);
34-
41+
3542
// 문의 리스트 조회
3643
List<InquiryResponse> inquiries = queryFactory
3744
.select(Projections.constructor(InquiryResponse.class,
@@ -45,26 +52,55 @@ public Page<InquiryResponse> getInquiryList(UUID memberPublicId,
4552
))
4653
.from(inquiry)
4754
.leftJoin(inquiryAnswer).on(inquiry.id.eq(inquiryAnswer.inquiry.id))
48-
.where(inquiry.member.publicId.eq(memberPublicId))
55+
.where(
56+
isMemberEqualTo(memberPublicId),
57+
getSearchCondition(searchType, keyword)
58+
)
4959
.orderBy(orderSpecifier)
5060
.offset(pageable.getOffset())
5161
.limit(pageable.getPageSize())
5262
.fetch();
5363

5464
// 전체 개수 조회
55-
long total = Optional.ofNullable(queryFactory
56-
.select(inquiry.count())
57-
.from(inquiry)
58-
.where(inquiry.member.publicId.eq(memberPublicId))
59-
.fetchOne()).orElse(0L);
65+
long total = getInquiryCount(memberPublicId, searchType, keyword);
6066

6167
return new PageImpl<>(inquiries, pageable, total);
6268
}
6369

70+
private long getInquiryCount(UUID memberPublicId,
71+
InquirySearchType searchType,
72+
String keyword) {
73+
return Optional.ofNullable(queryFactory
74+
.select(inquiry.count())
75+
.from(inquiry)
76+
.where(
77+
isMemberEqualTo(memberPublicId),
78+
getSearchCondition(searchType, keyword)
79+
)
80+
.fetchOne()
81+
).orElse(0L);
82+
}
83+
6484
private OrderSpecifier<?> getOrderSpecifier(InquiryOrderType orderType, QInquiry inquiry, QInquiryAnswer inquiryAnswer) {
6585
if (orderType == InquiryOrderType.ANSWERED) {
6686
return inquiryAnswer.answeredAt.desc().nullsLast();
6787
}
6888
return inquiry.createdAt.asc();
6989
}
90+
91+
private BooleanExpression isMemberEqualTo(UUID memberPublicId) {
92+
return memberPublicId != null ? inquiry.member.publicId.eq(memberPublicId) : null;
93+
}
94+
95+
private BooleanExpression getSearchCondition(InquirySearchType searchType, String keyword) {
96+
if (keyword == null || keyword.trim().isEmpty() || searchType == null) {
97+
return null;
98+
}
99+
100+
return switch (searchType) {
101+
case ANSWER -> inquiryAnswer.content.containsIgnoreCase(keyword);
102+
case CONTENT -> inquiry.content.containsIgnoreCase(keyword);
103+
default -> null;
104+
};
105+
}
70106
}

src/main/java/org/myteam/server/inquiry/service/InquiryReadService.java

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

33
import lombok.RequiredArgsConstructor;
44
import lombok.extern.slf4j.Slf4j;
5+
import org.myteam.server.global.page.response.PageCustomResponse;
6+
import org.myteam.server.inquiry.dto.request.InquirySearchRequest;
7+
import org.myteam.server.inquiry.dto.response.InquiryResponse;
8+
import org.myteam.server.inquiry.repository.InquiryQueryRepository;
59
import org.myteam.server.global.page.request.PageInfoRequest;
610
import org.myteam.server.global.page.response.PageCustomResponse;
711
import org.myteam.server.inquiry.domain.Inquiry;
@@ -12,25 +16,28 @@
1216
import org.myteam.server.member.entity.Member;
1317
import org.myteam.server.member.service.MemberReadService;
1418
import org.springframework.data.domain.Page;
15-
import org.springframework.data.domain.PageRequest;
16-
import org.springframework.data.domain.Pageable;
1719
import org.springframework.stereotype.Service;
1820
import org.springframework.transaction.annotation.Transactional;
1921

20-
import java.util.UUID;
21-
2222
@Slf4j
2323
@RequiredArgsConstructor
2424
@Service
2525
@Transactional(readOnly = true)
2626
public class InquiryReadService {
2727
private final InquiryQueryRepository inquiryQueryRepository;
2828

29-
public PageCustomResponse<InquiryResponse> getInquiriesByMember(InquiryFindRequest inquiryFindRequest) {
29+
/**
30+
* 검색 + 정렬 기능
31+
* @param inquirySearchRequest
32+
* @return
33+
*/
34+
public PageCustomResponse<InquiryResponse> getInquiriesByMember(InquirySearchRequest inquirySearchRequest) {
3035
Page<InquiryResponse> inquiryResponses = inquiryQueryRepository.getInquiryList(
31-
inquiryFindRequest.getMemberPublicId(),
32-
inquiryFindRequest.getOrderType(),
33-
inquiryFindRequest.toPageable()
36+
inquirySearchRequest.getMemberPublicId(),
37+
inquirySearchRequest.getOrderType(),
38+
inquirySearchRequest.getSearchType(),
39+
inquirySearchRequest.getKeyword(),
40+
inquirySearchRequest.toPageable()
3441
);
3542

3643
return PageCustomResponse.of(inquiryResponses);

src/test/java/org/myteam/server/inquiry/service/InquiryReadServiceTest.java

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
import org.junit.jupiter.api.DisplayName;
99
import org.junit.jupiter.api.Test;
1010
import org.myteam.server.IntegrationTestSupport;
11-
import org.myteam.server.global.page.request.PageInfoRequest;
1211
import org.myteam.server.global.page.response.PageCustomResponse;
12+
import org.myteam.server.inquiry.domain.Inquiry;
1313
import org.myteam.server.inquiry.domain.InquiryOrderType;
14+
import org.myteam.server.inquiry.domain.InquirySearchType;
15+
import org.myteam.server.inquiry.dto.request.InquirySearchRequest;
1416
import org.myteam.server.inquiry.dto.request.InquiryFindRequest;
1517
import org.myteam.server.inquiry.dto.response.InquiryResponse;
18+
import org.myteam.server.inquiry.repository.InquiryAnswerRepository;
1619
import org.myteam.server.inquiry.repository.InquiryRepository;
1720
import org.myteam.server.member.dto.MemberSaveRequest;
1821
import org.myteam.server.member.entity.Member;
@@ -21,9 +24,10 @@
2124
import org.springframework.beans.factory.annotation.Autowired;
2225
import org.springframework.boot.test.context.SpringBootTest;
2326

24-
import java.time.LocalDateTime;
25-
import java.util.*;
27+
import java.util.ArrayList;
28+
import java.util.List;
2629
import java.util.UUID;
30+
import java.util.stream.IntStream;
2731

2832
class InquiryReadServiceTest extends IntegrationTestSupport {
2933

@@ -39,6 +43,11 @@ class InquiryReadServiceTest extends IntegrationTestSupport {
3943
@Autowired
4044
private InquiryReadService inquiryReadService;
4145

46+
@Autowired
47+
private InquiryAnswerWriteService inquiryAnswerWriteService;
48+
@Autowired
49+
private InquiryAnswerRepository inquiryAnswerRepository;
50+
4251
private Member testMember;
4352
private Member otherMember;
4453

@@ -59,36 +68,98 @@ void setUp() {
5968
.nickname("otherUser")
6069
.password("otherMember!@#")
6170
.build()).getPublicId();
71+
72+
testMember = memberRepository.findByPublicId(testMemberPublicId).get();
73+
IntStream.rangeClosed(1, 15).forEach(i ->
74+
inquiryWriteService.createInquiry("문의내역 " + i, testMemberPublicId, "127.0.0.1")
75+
);
76+
otherMember = memberRepository.findByPublicId(otherMemberPublicId).get();
77+
IntStream.rangeClosed(1, 15).forEach(i ->
78+
inquiryWriteService.createInquiry("건의사항 " + i, otherMemberPublicId, "127.0.0.1")
79+
);
6280
}
6381

6482
@AfterEach
6583
void cleanUp() {
84+
inquiryAnswerRepository.deleteAllInBatch();
6685
inquiryRepository.deleteAllInBatch();
6786
memberJpaRepository.deleteAll();
6887
}
6988

7089
@Test
71-
@DisplayName("회원의 문의 내역을 정상적으로 조회한다.")
90+
@DisplayName("최신순으로 회원의 문의 내역을 조회한다.")
7291
void shouldReturnPagedInquiriesForMember() {
7392
// Given
74-
testMember = memberRepository.findByPublicId(testMemberPublicId).get();
75-
for (int i = 1; i <= 15; i++) {
76-
inquiryWriteService.createInquiry("문의내역 " + i, testMemberPublicId, "127.0.0.1");
93+
94+
// When
95+
PageCustomResponse<InquiryResponse> response = inquiryReadService.getInquiriesByMember(
96+
new InquirySearchRequest(
97+
testMember.getPublicId(),
98+
InquiryOrderType.RECENT,
99+
null,
100+
null,
101+
2,
102+
5));
103+
104+
// Then
105+
System.out.println("response: " + response);
106+
assertThat("문의내역 10").isEqualTo(response.getContent().get(0).getContent());
107+
assertThat(response.getContent()).hasSize(5);
108+
assertThat(response.getPageInfo().getCurrentPage()).isEqualTo(2);
109+
assertThat(response.getPageInfo().getTotalPage()).isEqualTo(3);
110+
assertThat(response.getPageInfo().getTotalElement()).isEqualTo(15);
111+
}
112+
113+
@Test
114+
@DisplayName("답변일 기준으로 회원의 문의 내역을 조회한다.")
115+
void shouldReturnPagedInquiriesSortedByAnswered() {
116+
// Given
117+
118+
// 일부 문의에 답변 추가
119+
for (int i = 1; i <= 5; i++) {
120+
inquiryAnswerWriteService.createAnswer(Long.valueOf(i), "답변 " + i);
77121
}
78-
otherMember = memberRepository.findByPublicId(otherMemberPublicId).get();
122+
123+
// When
124+
PageCustomResponse<InquiryResponse> response = inquiryReadService.getInquiriesByMember(
125+
new InquirySearchRequest(
126+
testMember.getPublicId(),
127+
InquiryOrderType.ANSWERED,
128+
null,
129+
null,
130+
1, 5));
131+
132+
// Then
133+
System.out.println("response: " + response);
134+
assertThat(response.getContent().get(0).getAnswerContent()).isEqualTo("답변 5");
135+
assertThat(response.getContent()).hasSize(5);
136+
assertThat(response.getPageInfo().getCurrentPage()).isEqualTo(1);
137+
assertThat(response.getPageInfo().getTotalPage()).isEqualTo(3);
138+
assertThat(response.getPageInfo().getTotalElement()).isEqualTo(15);
139+
}
140+
141+
@Test
142+
@DisplayName("문의 내용을 검색하고 최신순으로 조회한다.")
143+
void shouldSearchInquiriesByContentAndSortByRecent() {
144+
// Given
79145
for (int i = 1; i <= 15; i++) {
80-
inquiryWriteService.createInquiry("건의사항 " + i, otherMemberPublicId, "127.0.0.1");
146+
inquiryWriteService.createInquiry("검색어 포함 " + i, testMemberPublicId, "127.0.0.1");
81147
}
82148

83149
// When
84-
PageCustomResponse<InquiryResponse> response = inquiryReadService.getInquiriesByMember(new InquiryFindRequest(testMember.getPublicId(), InquiryOrderType.RECENT, 2, 5));
150+
PageCustomResponse<InquiryResponse> response = inquiryReadService.getInquiriesByMember(
151+
new InquirySearchRequest(
152+
testMember.getPublicId(),
153+
InquiryOrderType.RECENT,
154+
InquirySearchType.CONTENT,
155+
"검색어", // ✅ 검색어 적용
156+
1, 5));
85157

86158
// Then
87-
System.out.println(response);
88-
assertThat("문의내역 6").isEqualTo(response.getContent().get(0).getContent());
159+
System.out.println("response: " + response);
160+
assertThat(response.getContent().get(0).getContent()).contains("검색어 포함 15"); // ✅ 최신순 확인
89161
assertThat(response.getContent()).hasSize(5);
90-
assertThat(response.getPageInfo().getCurrentPage()).isEqualTo(2);
162+
assertThat(response.getPageInfo().getCurrentPage()).isEqualTo(1);
91163
assertThat(response.getPageInfo().getTotalPage()).isEqualTo(3);
92-
assertThat(response.getPageInfo().getTotalElement()).isEqualTo(15);
93164
}
94165
}

src/test/java/org/myteam/server/inquiry/service/InquiryWriteServiceTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44

5-
import org.junit.jupiter.api.AfterEach;
65
import org.junit.jupiter.api.BeforeEach;
76
import org.junit.jupiter.api.DisplayName;
87
import org.junit.jupiter.api.Test;

0 commit comments

Comments
 (0)