Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat-be: 이메일 발송 내역 조회 기능 #974

Merged
merged 3 commits into from
Jan 4, 2025
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
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);
}
}
Loading