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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ public class SecurityConfig {
"/api/oauth2/members/email/**",
"/api/members/type/**",
"/api/me/create",
TOKEN_REISSUE_PATH
TOKEN_REISSUE_PATH,

// 문의하기
"/api/inquiries/create",
};
/* Admin 접근 권한 */
private static final String[] PERMIT_ADMIN_URLS = new String[]{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.myteam.server.inquiry.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.myteam.server.global.page.request.PageInfoRequest;
import org.myteam.server.global.page.response.PageCustomResponse;
import org.myteam.server.global.web.response.ResponseDto;
import org.myteam.server.inquiry.domain.Inquiry;
import org.myteam.server.inquiry.dto.request.InquiryRequest;
import org.myteam.server.inquiry.dto.response.InquiryResponse;
import org.myteam.server.inquiry.service.InquiryReadService;
import org.myteam.server.inquiry.service.InquiryWriteService;
import org.myteam.server.util.ClientUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

import static org.myteam.server.global.web.response.ResponseStatus.SUCCESS;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/inquiries")
public class InquiryController {
private final InquiryReadService inquiryReadService;
private final InquiryWriteService inquiryWriteService;

@PostMapping()
public ResponseEntity<ResponseDto<String>> createInquiry(@Valid @RequestBody InquiryRequest inquiryRequest,
HttpServletRequest request) {
String clientIp = ClientUtils.getRemoteIP(request);
String content = inquiryWriteService.createInquiry(inquiryRequest.getContent(), inquiryRequest.getMemberPublicId(), clientIp);

return ResponseEntity.ok(new ResponseDto<>(
SUCCESS.name(),
"Successfully upload inquiry",
content
));
}

@GetMapping("/my")
public ResponseEntity<ResponseDto<PageCustomResponse<InquiryResponse>>> getMyInquiries(@RequestParam UUID memberPublicId, PageInfoRequest pageInfoRequest) {
PageCustomResponse<InquiryResponse> content = inquiryReadService.getInquiriesByMember(memberPublicId, pageInfoRequest);

return ResponseEntity.ok(new ResponseDto<>(
SUCCESS.name(),
"Successfully find inquiries",
content
));
}
}
31 changes: 31 additions & 0 deletions src/main/java/org/myteam/server/inquiry/domain/Inquiry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.myteam.server.inquiry.domain;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import org.myteam.server.member.entity.Member;

import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Inquiry {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String content;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid체크를 Entity에 하신이유가 있으실까요?
Valid 체크는 Controller Request단에서 하는게 맞는거 같습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 반영하겠습니다


@ManyToOne(optional = true, cascade = CascadeType.REMOVE)
@JoinColumn(name = "member_id")
private Member member;

private String clientIp;

@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.myteam.server.inquiry.dto.request;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;

import java.util.UUID;

@Getter
public class InquiryRequest {
@NotNull(message = "문의 내용이 없으면 안됩니다.")
private String content;
private UUID memberPublicId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.myteam.server.inquiry.dto.response;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import org.myteam.server.inquiry.domain.Inquiry;

import java.time.LocalDateTime;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InquiryResponse {
private Long id;
private String content;
private String memberNickname;
private String clientIp;
private LocalDateTime createdAt;

// Entity -> DTO 변환 메서드
public static InquiryResponse createInquiryResponse(Inquiry inquiry) {
return InquiryResponse.builder()
.id(inquiry.getId())
.content(inquiry.getContent())
.memberNickname(inquiry.getMember().getNickname())
.createdAt(inquiry.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.myteam.server.inquiry.repository;

import org.myteam.server.inquiry.domain.Inquiry;
import org.myteam.server.member.entity.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;


@Repository
public interface InquiryRepository extends JpaRepository<Inquiry, Long> {
Page<Inquiry> findByMember(Member member, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.myteam.server.inquiry.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.myteam.server.global.exception.ErrorCode;
import org.myteam.server.global.exception.PlayHiveException;
import org.myteam.server.global.page.request.PageInfoRequest;
import org.myteam.server.global.page.response.PageCustomResponse;
import org.myteam.server.inquiry.domain.Inquiry;
import org.myteam.server.inquiry.dto.response.InquiryResponse;
import org.myteam.server.inquiry.repository.InquiryRepository;
import org.myteam.server.member.entity.Member;
import org.myteam.server.member.repository.MemberRepository;
import org.myteam.server.member.service.MemberReadService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class InquiryReadService {
private final MemberReadService memberReadService;
private final InquiryRepository inquiryRepository;

public PageCustomResponse<InquiryResponse> getInquiriesByMember(UUID memberPublicId, PageInfoRequest pageInfoRequest) {
Member member = memberReadService.findById(memberPublicId);

Pageable pageable = PageRequest.of(pageInfoRequest.getPage() - 1, pageInfoRequest.getSize());
Page<Inquiry> inquiries = inquiryRepository.findByMember(member, pageable);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entity를 바로 Response로 내려주는건 안좋습니다ㅠ InquiryResponse를 만들어 변환해줘서 내려주는게 좋습니다!

https://simplifyprocess.tistory.com/26

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

죄송합니다 저번에 피드백 주신 부분인데 또 반영을 못했네요,, 수정하도록 하겠습니다.

Page<InquiryResponse> inquiryResponses = inquiries.map(InquiryResponse::createInquiryResponse);

return PageCustomResponse.of(inquiryResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.myteam.server.inquiry.service;


import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.myteam.server.inquiry.domain.Inquiry;
import org.myteam.server.inquiry.repository.InquiryRepository;
import org.myteam.server.member.entity.Member;
import org.myteam.server.member.repository.MemberRepository;
import org.myteam.server.util.slack.service.SlackService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;

@Slf4j
@RequiredArgsConstructor
@Service
@Transactional
public class InquiryWriteService {

private final MemberRepository memberRepository;
private final InquiryRepository inquiryRepository;
private final SlackService slackService;

public String createInquiry(String content, UUID memberPublicId, String clientIP) {
Optional<Member> member = memberRepository.findByPublicId(memberPublicId);

Inquiry inquiry = Inquiry.builder()
.content(content)
.member(member.orElse(null))
.clientIp(clientIP)
.createdAt(LocalDateTime.now())
.build();

String slackMessage = String.format(
"📩 새로운 문의가 접수되었습니다!\n\n" +
"🔹 문의 내용: %s\n" +
"🔹 작성자: %s (%s)\n" +
"🔹 요청 IP: %s\n" +
"🔹 작성 시간: %s\n\n" +
"확인 후 답변해 주세요. ✅",
content,
member.map(Member::getNickname).orElse("익명 사용자"),
member.map(Member::getEmail).orElse("익명"),
clientIP,
LocalDateTime.now()
);

inquiryRepository.save(inquiry);
slackService.sendSlackNotification(slackMessage);

log.info("문의 내용이 접수되었습니다.");

return inquiry.getContent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.myteam.server.inquiry.service;


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

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.myteam.server.global.page.request.PageInfoRequest;
import org.myteam.server.global.page.response.PageCustomResponse;
import org.myteam.server.inquiry.domain.Inquiry;
import org.myteam.server.inquiry.dto.response.InquiryResponse;
import org.myteam.server.member.dto.MemberSaveRequest;
import org.myteam.server.member.entity.Member;
import org.myteam.server.member.repository.MemberRepository;
import org.myteam.server.member.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDateTime;
import java.util.*;
import java.util.UUID;

@SpringBootTest
class InquiryReadServiceTest {

@Autowired
private MemberService memberService;
@Autowired
private MemberRepository memberRepository;

@Autowired
private InquiryWriteService inquiryWriteService;
@Autowired
private InquiryReadService inquiryReadService;

private Member testMember;
private Member otherMember;

private UUID testMemberPublicId;
private UUID otherMemberPublicId;

@BeforeEach
void setUp() {
testMemberPublicId = memberService.create(MemberSaveRequest.builder()
.email("[email protected]")
.tel("01012345678")
.nickname("testUser")
.password("teamPlayHive12#")
.build()).getPublicId();
otherMemberPublicId = memberService.create(MemberSaveRequest.builder()
.email("[email protected]")
.tel("01087654321")
.nickname("otherUser")
.password("otherMember!@#")
.build()).getPublicId();

System.out.println("testMemberPublicId = " + testMemberPublicId);
System.out.println("otherMemberPublicId = " + otherMemberPublicId);
}

@Test
@DisplayName("회원의 문의 내역을 정상적으로 조회한다.")
void shouldReturnPagedInquiriesForMember() {
// Given
testMember = memberRepository.findByPublicId(testMemberPublicId).get();
for (int i = 1; i <= 15; i++) {
inquiryWriteService.createInquiry("문의내역 " + i, testMemberPublicId, "127.0.0.1");
}
otherMember = memberRepository.findByPublicId(otherMemberPublicId).get();
for (int i = 1; i <= 15; i++) {
inquiryWriteService.createInquiry("건의사항 " + i, otherMemberPublicId, "127.0.0.1");
}

// When
PageCustomResponse<InquiryResponse> response = inquiryReadService.getInquiriesByMember(testMember.getPublicId(), new PageInfoRequest(2, 5));

// Then
System.out.println(response);
assertThat("문의내역 6").isEqualTo(response.getContent().get(0).getContent());
assertThat(response.getContent()).hasSize(5);
assertThat(response.getPageInfo().getCurrentPage()).isEqualTo(2);
assertThat(response.getPageInfo().getTotalPage()).isEqualTo(3);
assertThat(response.getPageInfo().getTotalElement()).isEqualTo(15);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertThat(pageInfo)
.extracting("currentPage", "totalPage", "totalElement")
.containsExactlyInAnyOrder(
	1, 2, 3L
)

이런식으로 한번에 처리도 가능합니다! 참고해주세요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertThat(pageInfo)
.extracting("currentPage", "totalPage", "totalElement")
.containsExactlyInAnyOrder(
1, 2, 3L
)
아 위에서 답변주신 부분이 이렇게 사용하는거군요.
많이 배웁니다 감사합니다 :)

}
}
Loading