diff --git a/backend/src/docs/asciidoc/email.adoc b/backend/src/docs/asciidoc/email.adoc index 3bb3f2f32..8f77a6084 100644 --- a/backend/src/docs/asciidoc/email.adoc +++ b/backend/src/docs/asciidoc/email.adoc @@ -45,3 +45,17 @@ operation::email/verify-code-fail/code-not-found[snippets="http-request,request- ==== 실패: 인증 번호가 다른 이메일 operation::email/verify-code-fail/code-mismatch[snippets="http-request,request-fields,http-response"] + +=== 이메일 전송 내역 조회 + +==== 성공 + +operation::email/read[snippets="http-request,request-cookies,path-parameters,http-response,response-fields"] + +==== 실패: 존재하지 않는 동아리 + +operation::email/read-fail/club-not-found[snippets="http-request,request-cookies,path-parameters,http-response"] + +==== 실패: 존재하지 않는 지원자 + +operation::email/read-fail/applicant-not-found[snippets="http-request,request-cookies,path-parameters,http-response"] diff --git a/backend/src/main/java/com/cruru/email/controller/EmailController.java b/backend/src/main/java/com/cruru/email/controller/EmailController.java index f478d24e1..0728020a1 100644 --- a/backend/src/main/java/com/cruru/email/controller/EmailController.java +++ b/backend/src/main/java/com/cruru/email/controller/EmailController.java @@ -1,15 +1,20 @@ package com.cruru.email.controller; +import com.cruru.auth.annotation.RequireAuth; import com.cruru.auth.annotation.ValidAuth; +import com.cruru.club.domain.Club; import com.cruru.email.controller.request.EmailRequest; import com.cruru.email.controller.request.SendVerificationCodeRequest; import com.cruru.email.controller.request.VerifyCodeRequest; +import com.cruru.email.controller.response.EmailResponses; import com.cruru.email.facade.EmailFacade; import com.cruru.global.LoginProfile; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -45,4 +50,15 @@ public ResponseEntity verifyCode(@Valid @RequestBody VerifyCodeRequest req emailFacade.verifyCode(request); return ResponseEntity.ok().build(); } + + @GetMapping("/{clubId}/{applicantId}") + @ValidAuth + public ResponseEntity read( + @RequireAuth(targetDomain = Club.class) @PathVariable Long clubId, + @PathVariable Long applicantId, + LoginProfile loginProfile + ) { + EmailResponses emailResponses = emailFacade.read(clubId, applicantId); + return ResponseEntity.ok(emailResponses); + } } diff --git a/backend/src/main/java/com/cruru/email/controller/response/EmailResponse.java b/backend/src/main/java/com/cruru/email/controller/response/EmailResponse.java new file mode 100644 index 000000000..7377a954f --- /dev/null +++ b/backend/src/main/java/com/cruru/email/controller/response/EmailResponse.java @@ -0,0 +1,12 @@ +package com.cruru.email.controller.response; + +import java.time.LocalDateTime; + +public record EmailResponse( + String subject, + String content, + LocalDateTime createdDate, + Boolean isSucceed +) { + +} diff --git a/backend/src/main/java/com/cruru/email/controller/response/EmailResponses.java b/backend/src/main/java/com/cruru/email/controller/response/EmailResponses.java new file mode 100644 index 000000000..d187491d1 --- /dev/null +++ b/backend/src/main/java/com/cruru/email/controller/response/EmailResponses.java @@ -0,0 +1,8 @@ +package com.cruru.email.controller.response; + +import java.util.List; + +public record EmailResponses( + List emailResponses +) { +} diff --git a/backend/src/main/java/com/cruru/email/domain/repository/EmailRepository.java b/backend/src/main/java/com/cruru/email/domain/repository/EmailRepository.java index c7aa2d9a6..cddcbb33b 100644 --- a/backend/src/main/java/com/cruru/email/domain/repository/EmailRepository.java +++ b/backend/src/main/java/com/cruru/email/domain/repository/EmailRepository.java @@ -1,6 +1,7 @@ package com.cruru.email.domain.repository; import com.cruru.applicant.domain.Applicant; +import com.cruru.club.domain.Club; import com.cruru.email.domain.Email; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,6 +12,8 @@ public interface EmailRepository extends JpaRepository { + List findAllByFromAndTo(Club from, Applicant to); + @Modifying(clearAutomatically = true, flushAutomatically = true) @Transactional @Query("DELETE FROM Email e WHERE e.to IN :tos") diff --git a/backend/src/main/java/com/cruru/email/facade/EmailFacade.java b/backend/src/main/java/com/cruru/email/facade/EmailFacade.java index b3a743c56..46dde166d 100644 --- a/backend/src/main/java/com/cruru/email/facade/EmailFacade.java +++ b/backend/src/main/java/com/cruru/email/facade/EmailFacade.java @@ -7,6 +7,9 @@ import com.cruru.email.controller.request.EmailRequest; import com.cruru.email.controller.request.SendVerificationCodeRequest; import com.cruru.email.controller.request.VerifyCodeRequest; +import com.cruru.email.controller.response.EmailResponse; +import com.cruru.email.controller.response.EmailResponses; +import com.cruru.email.domain.Email; import com.cruru.email.exception.EmailAttachmentsException; import com.cruru.email.exception.EmailConflictException; import com.cruru.email.service.EmailRedisClient; @@ -80,4 +83,22 @@ public void verifyCode(VerifyCodeRequest request) { VerificationCodeUtil.verify(storedVerificationCode, inputVerificationCode); emailRedisClient.saveVerifiedEmail(email); } + + public EmailResponses read(long clubId, long applicantId) { + Club club = clubService.findById(clubId); + Applicant applicant = applicantService.findById(applicantId); + List emails = emailService.findAllByFromAndTo(club, applicant); + return new EmailResponses(emails.stream() + .map(this::toEmailResponse) + .toList()); + } + + private EmailResponse toEmailResponse(Email email) { + return new EmailResponse( + email.getSubject(), + email.getContent(), + email.getCreatedDate(), + email.getIsSucceed() + ); + } } diff --git a/backend/src/main/java/com/cruru/email/service/EmailService.java b/backend/src/main/java/com/cruru/email/service/EmailService.java index 386e8dec5..5718bd5e5 100644 --- a/backend/src/main/java/com/cruru/email/service/EmailService.java +++ b/backend/src/main/java/com/cruru/email/service/EmailService.java @@ -97,4 +97,8 @@ public void sendVerificationCode(String to, String verificationCode) { log.error("이메일 전송 실패: to={}, subject={}", to, e.getMessage()); } } + + public List findAllByFromAndTo(Club from, Applicant to) { + return emailRepository.findAllByFromAndTo(from, to); + } } diff --git a/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java b/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java index d85fc7489..12b1f8f3d 100644 --- a/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java +++ b/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java @@ -6,7 +6,10 @@ import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.partWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.requestParts; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; @@ -14,6 +17,7 @@ import com.cruru.applicant.domain.repository.ApplicantRepository; import com.cruru.email.controller.request.SendVerificationCodeRequest; import com.cruru.email.controller.request.VerifyCodeRequest; +import com.cruru.email.domain.repository.EmailRepository; import com.cruru.email.service.EmailRedisClient; import com.cruru.member.domain.repository.MemberRepository; import com.cruru.util.ControllerTest; @@ -37,6 +41,9 @@ class EmailControllerTest extends ControllerTest { @Autowired private MemberRepository memberRepository; + @Autowired + private EmailRepository emailRepository; + @MockBean private EmailRedisClient emailRedisClient; @@ -288,4 +295,75 @@ void verifyCode_codeMisMatch() { .when().post("/v1/emails/verify-code") .then().log().all().statusCode(400); } + + @DisplayName("이메일 조회 성공 시, 200을 응답한다.") + @Test + void read() { + // given + Applicant applicant = applicantRepository.save(ApplicantFixture.pendingDobby()); + emailRepository.save(EmailFixture.rejectEmail(defaultClub, applicant)); + + // when&then + RestAssured.given(spec).log().all() + .cookie("accessToken", token) + .accept(ContentType.JSON) + .filter(document("email/read", + requestCookies(cookieWithName("accessToken").description("사용자 토큰")), + pathParameters( + parameterWithName("clubId").description("발송 동아리 id"), + parameterWithName("applicantId").description("수신 지원자 id") + ), + responseFields(fieldWithPath("emailResponses").description("이메일 응답들")) + .andWithPrefix("emailResponses[].", + fieldWithPath("subject").description("이메일 제목"), + fieldWithPath("content").description("이메일 본문"), + fieldWithPath("createdDate").description("전송 날짜"), + fieldWithPath("isSucceed").description("전송 성공 여부") + ) + )) + .when().get("/v1/emails/{clubId}/{applicantId}", defaultClub.getId(), applicant.getId()) + .then().log().all().statusCode(200); + } + + @DisplayName("존재하지 않는 동아리 id를 발송자로 조회한 경우 404를 응답한다.") + @Test + void read_clubNotFound() { + // given + Applicant applicant = applicantRepository.save(ApplicantFixture.pendingDobby()); + emailRepository.save(EmailFixture.rejectEmail(defaultClub, applicant)); + + // when&then + RestAssured.given(spec).log().all() + .cookie("accessToken", token) + .filter(document("email/read-fail/club-not-found", + requestCookies(cookieWithName("accessToken").description("사용자 토큰")), + pathParameters( + parameterWithName("clubId").description("존재하지 않는 발송 동아리 id"), + parameterWithName("applicantId").description("수신 지원자 id") + ) + )) + .when().get("/v1/emails/{clubId}/{applicantId}", -1, applicant.getId()) + .then().log().all().statusCode(404); + } + + @DisplayName("존재하지 않는 지원자 id를 수신자로 조회한 경우 404를 응답한다.") + @Test + void read_applicantNotFound() { + // given + Applicant applicant = applicantRepository.save(ApplicantFixture.pendingDobby()); + emailRepository.save(EmailFixture.rejectEmail(defaultClub, applicant)); + + // when&then + RestAssured.given(spec).log().all() + .cookie("accessToken", token) + .filter(document("email/read-fail/applicant-not-found", + requestCookies(cookieWithName("accessToken").description("사용자 토큰")), + pathParameters( + parameterWithName("clubId").description("발송 동아리 id"), + parameterWithName("applicantId").description("존재하지 않는 수신 지원자 id") + ) + )) + .when().get("/v1/emails/{clubId}/{applicantId}", defaultClub.getId(), -1) + .then().log().all().statusCode(404); + } } diff --git a/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java b/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java index 4f0944b1b..6762d0d14 100644 --- a/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java +++ b/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java @@ -1,7 +1,9 @@ package com.cruru.email.facade; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -12,7 +14,10 @@ import com.cruru.email.controller.request.EmailRequest; import com.cruru.email.controller.request.SendVerificationCodeRequest; import com.cruru.email.controller.request.VerifyCodeRequest; +import com.cruru.email.controller.response.EmailResponse; +import com.cruru.email.controller.response.EmailResponses; import com.cruru.email.domain.Email; +import com.cruru.email.domain.repository.EmailRepository; import com.cruru.email.exception.EmailConflictException; import com.cruru.email.exception.badrequest.VerificationCodeMismatchException; import com.cruru.email.exception.badrequest.VerificationCodeNotFoundException; @@ -45,6 +50,9 @@ class EmailFacadeTest extends ServiceTest { @Autowired private MemberRepository memberRepository; + @Autowired + private EmailRepository emailRepository; + @Autowired private EmailFacade emailFacade; @@ -142,4 +150,24 @@ void verifyCode_verificationCodeMismatchException() { .isInstanceOf(VerificationCodeMismatchException.class) .hasMessage("인증 코드가 일치하지 않습니다."); } + + @DisplayName("동아리와 지원자 id로 이메일을 조회한다.") + @Test + void read() { + // given + Applicant applicant = applicantRepository.save(ApplicantFixture.pendingDobby()); + Email email = emailRepository.save(EmailFixture.rejectEmail(defaultClub, applicant)); + + // when + EmailResponses emailResponses = emailFacade.read(defaultClub.getId(), applicant.getId()); + + // then + assertThat(emailResponses.emailResponses()).hasSize(1); + EmailResponse emailResponse = emailResponses.emailResponses().get(0); + assertAll( + () -> assertThat(emailResponse.subject()).isEqualTo(email.getSubject()), + () -> assertThat(emailResponse.content()).isEqualTo(email.getContent()), + () -> assertThat(emailResponse.isSucceed()).isEqualTo(email.getIsSucceed()) + ); + } } diff --git a/backend/src/test/java/com/cruru/email/service/EmailServiceTest.java b/backend/src/test/java/com/cruru/email/service/EmailServiceTest.java index 507629a97..354166925 100644 --- a/backend/src/test/java/com/cruru/email/service/EmailServiceTest.java +++ b/backend/src/test/java/com/cruru/email/service/EmailServiceTest.java @@ -67,4 +67,19 @@ void deleteAllByTos() { assertThat(emailRepository.findAll()).contains(email3) .doesNotContain(email1, email2); } + + @DisplayName("동아리와 지원자 id로 이메일을 조회한다.") + @Test + void findAllByFromAndTo() { + // given + Applicant applicant = applicantRepository.save(ApplicantFixture.pendingDobby()); + Email email = emailRepository.save(EmailFixture.rejectEmail(defaultClub, applicant)); + + // when + List founds = emailService.findAllByFromAndTo(email.getFrom(), email.getTo()); + + // then + assertThat(founds).hasSize(1); + assertThat(founds.get(0)).isEqualTo(email); + } }