Skip to content

Commit

Permalink
feat-be: 이메일 발송 내역 조회 기능 (#974)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: HyungHoKim00 <hkim1109@naver.com>
  • Loading branch information
github-actions[bot] and HyungHoKim00 authored Jan 4, 2025
1 parent d63a3b3 commit f6aaf78
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 0 deletions.
14 changes: 14 additions & 0 deletions backend/src/docs/asciidoc/email.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.cruru.email.controller;

import com.cruru.applicant.domain.Applicant;
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.EmailHistoryResponses;
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;
Expand Down Expand Up @@ -45,4 +51,15 @@ public ResponseEntity<Void> verifyCode(@Valid @RequestBody VerifyCodeRequest req
emailFacade.verifyCode(request);
return ResponseEntity.ok().build();
}

@GetMapping("/{clubId}/{applicantId}")
@ValidAuth
public ResponseEntity<EmailHistoryResponses> read(
@RequireAuth(targetDomain = Club.class) @PathVariable Long clubId,
@RequireAuth(targetDomain = Applicant.class) @PathVariable Long applicantId,
LoginProfile loginProfile
) {
EmailHistoryResponses emailHistoryResponses = emailFacade.read(clubId, applicantId);
return ResponseEntity.ok(emailHistoryResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.cruru.email.controller.response;

import java.time.LocalDateTime;

public record EmailHistoryResponse(
String subject,
String content,
LocalDateTime createdDate,
Boolean isSucceed
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.cruru.email.controller.response;

import java.util.List;

public record EmailHistoryResponses(
List<EmailHistoryResponse> emailHistoryResponses
) {
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,6 +12,8 @@

public interface EmailRepository extends JpaRepository<Email, Long> {

List<Email> findAllByFromAndTo(Club from, Applicant to);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Transactional
@Query("DELETE FROM Email e WHERE e.to IN :tos")
Expand Down
21 changes: 21 additions & 0 deletions backend/src/main/java/com/cruru/email/facade/EmailFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.EmailHistoryResponse;
import com.cruru.email.controller.response.EmailHistoryResponses;
import com.cruru.email.domain.Email;
import com.cruru.email.exception.EmailAttachmentsException;
import com.cruru.email.exception.EmailConflictException;
import com.cruru.email.service.EmailRedisClient;
Expand Down Expand Up @@ -80,4 +83,22 @@ public void verifyCode(VerifyCodeRequest request) {
VerificationCodeUtil.verify(storedVerificationCode, inputVerificationCode);
emailRedisClient.saveVerifiedEmail(email);
}

public EmailHistoryResponses read(long clubId, long applicantId) {
Club club = clubService.findById(clubId);
Applicant applicant = applicantService.findById(applicantId);
List<Email> emails = emailService.findAllByFromAndTo(club, applicant);
return new EmailHistoryResponses(emails.stream()
.map(this::toEmailResponse)
.toList());
}

private EmailHistoryResponse toEmailResponse(Email email) {
return new EmailHistoryResponse(
email.getSubject(),
email.getContent(),
email.getCreatedDate(),
email.getIsSucceed()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,8 @@ public void sendVerificationCode(String to, String verificationCode) {
log.error("이메일 전송 실패: to={}, subject={}", to, e.getMessage());
}
}

public List<Email> findAllByFromAndTo(Club from, Applicant to) {
return emailRepository.findAllByFromAndTo(from, to);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
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;

import com.cruru.applicant.domain.Applicant;
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;
Expand All @@ -37,6 +41,9 @@ class EmailControllerTest extends ControllerTest {
@Autowired
private MemberRepository memberRepository;

@Autowired
private EmailRepository emailRepository;

@MockBean
private EmailRedisClient emailRedisClient;

Expand Down Expand Up @@ -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("emailHistoryResponses").description("이메일 응답들"))
.andWithPrefix("emailHistoryResponses[].",
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);
}
}
28 changes: 28 additions & 0 deletions backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.EmailHistoryResponse;
import com.cruru.email.controller.response.EmailHistoryResponses;
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;
Expand Down Expand Up @@ -45,6 +50,9 @@ class EmailFacadeTest extends ServiceTest {
@Autowired
private MemberRepository memberRepository;

@Autowired
private EmailRepository emailRepository;

@Autowired
private EmailFacade emailFacade;

Expand Down Expand Up @@ -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
EmailHistoryResponses emailHistoryResponses = emailFacade.read(defaultClub.getId(), applicant.getId());

// then
assertThat(emailHistoryResponses.emailHistoryResponses()).hasSize(1);
EmailHistoryResponse emailHistoryResponse = emailHistoryResponses.emailHistoryResponses().get(0);
assertAll(
() -> assertThat(emailHistoryResponse.subject()).isEqualTo(email.getSubject()),
() -> assertThat(emailHistoryResponse.content()).isEqualTo(email.getContent()),
() -> assertThat(emailHistoryResponse.isSucceed()).isEqualTo(email.getIsSucceed())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Email> founds = emailService.findAllByFromAndTo(email.getFrom(), email.getTo());

// then
assertThat(founds).hasSize(1);
assertThat(founds.get(0)).isEqualTo(email);
}
}

0 comments on commit f6aaf78

Please sign in to comment.